diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..13e4e05b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,9 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + target-branch: "main" + commit-message: + prefix: "ci" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 005b6a2c..d01e8bf9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,17 +7,55 @@ name: CI on: [push, pull_request] jobs: - build: + linter: runs-on: ubuntu-latest strategy: matrix: - python-version: [2.7, 3.5, 3.6, 3.7, 3.8] + python-version: ["3.x"] + steps: + - uses: actions/checkout@v6 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt -r test-requirements.txt setuptools + - name: Lint + run: | + pylint --output-format colorized --rcfile .pylintrc \ + bugzilla-cli setup.py bugzilla examples tests + test_3_6: + # python 3.6 is for rhel/centos8/sles15 compat + runs-on: ubuntu-latest + container: + image: python:3.6 + steps: + - uses: actions/checkout@v6 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest + pip install -r requirements.txt -r test-requirements.txt + + - name: Test with pytest + run: | + pytest + + + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} @@ -39,37 +77,71 @@ jobs: pytest --cov --cov-report=xml - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v5 with: - file: ./coverage.xml - flags: unittests + token: ${{ secrets.CODECOV_TOKEN }} - # Build the RPM on latest fedora, centos7 and centos8 - rpm: + # Run functional tests + integration: runs-on: ubuntu-latest + services: + mariadb: + image: mariadb:latest + env: + MARIADB_USER: bugs + MARIADB_DATABASE: bugs + MARIADB_PASSWORD: secret + MARIADB_ROOT_PASSWORD: supersecret + ports: + - 3306:3306 + bugzilla: + image: ghcr.io/crazyscientist/bugzilla:test + ports: + - 80:80 + strategy: + matrix: + python-version: ["3.x"] + steps: + - uses: actions/checkout@v6 + - name: Install MariaDB utils + run: sudo apt install --no-install-recommends -q -y mariadb-client + - name: Restore DB dump + run: mariadb -h 127.0.0.1 -P 3306 --password=secret -u bugs bugs < tests/services/bugs.sql + - name: Store API key + run: | + mkdir -p ~/.config/python-bugzilla/ + cp tests/services/bugzillarc ~/.config/python-bugzilla/ + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest pytest-cov + pip install -r requirements.txt -r test-requirements.txt + - name: Test with pytest + run: pytest --ro-integration --rw-integration + env: + BUGZILLA_URL: http://localhost - container: - image: fedora:latest - # All this is needed to ensure 'mock' works in docker - options: --cap-add=SYS_ADMIN --security-opt label:disable --security-opt seccomp=unconfined --security-opt apparmor:unconfined + # Build and install on Windows + windows: + runs-on: windows-latest + strategy: + matrix: + python-version: ["3.x"] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - - name: Install deps - run: | - # glibc-langpacks-en needed to work around python locale issues - dnf install -y \ - python3-pip \ - rpm-build \ - mock \ - dnf-plugins-core \ - glibc-langpack-en - dnf builddep -y ./*.spec - - - name: Build RPM + SRPM + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + + - name: Build tarball & install run: | - ./setup.py rpm + python setup.py sdist - - run: mock --root epel-7-x86_64 *.src.rpm - - run: mock --root epel-8-x86_64 *.src.rpm + pip install --find-links dist python-bugzilla diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..3b537cb9 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,32 @@ +# This workflow will publish the package on PyPI +# For more information see: https://github.com/pypa/gh-action-pypi-publish + +name: Publish +on: + release: + types: [released] + +jobs: + publish: + name: Upload release to PyPI + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/python-bugzilla + permissions: + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing + steps: + - uses: actions/checkout@v6 + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.x" + - name: Install pypa/build + run: pip install build + - name: Build a source tarball + run: python -m build --sdist + - name: Publish package distributions to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + skip-existing: false + verbose: false diff --git a/.gitignore b/.gitignore index 1f832a6b..a3f16c2f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,3 @@ build .coverage .tox .pytest_cache -man/bugzilla.1 diff --git a/.packit.yml b/.packit.yml new file mode 100644 index 00000000..aefa13b9 --- /dev/null +++ b/.packit.yml @@ -0,0 +1,16 @@ +--- +upstream_project_url: https://github.com/python-bugzilla/python-bugzilla + +jobs: + - job: copr_build + trigger: commit + metadata: + targets: + - fedora-all + - epel-8-x86_64 + - job: tests + trigger: commit + metadata: + targets: + - fedora-all + - epel-8-x86_64 diff --git a/pylintrc b/.pylintrc similarity index 71% rename from pylintrc rename to .pylintrc index 23122933..378199f1 100644 --- a/pylintrc +++ b/.pylintrc @@ -7,7 +7,7 @@ persistent=no # can either give multiple identifier separated by comma (,) or put this option # multiple time (only on the command line, not in the configuration file where # it should appear only once). -disable=Design,Format,Similarities,invalid-name,missing-docstring,locally-disabled,unnecessary-lambda,star-args,fixme,global-statement,broad-except,no-self-use,bare-except,locally-enabled,wrong-import-position,consider-using-ternary,len-as-condition,no-else-return,useless-object-inheritance,inconsistent-return-statements,consider-using-dict-comprehension,import-outside-toplevel +disable=Design,Format,Similarities,invalid-name,missing-docstring,locally-disabled,unnecessary-lambda,fixme,global-statement,broad-except,bare-except,wrong-import-position,consider-using-ternary,len-as-condition,no-else-return,useless-object-inheritance,inconsistent-return-statements,consider-using-dict-comprehension,import-outside-toplevel,use-a-generator,consider-using-with,consider-using-f-string,unspecified-encoding,use-implicit-booleaness-not-comparison enable=fixme @@ -19,7 +19,7 @@ score=no [FORMAT] # Maximum number of characters on a single line. -max-line-length=80 +max-line-length=100 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 # tab). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8f8f24f0..c3943334 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,17 +24,17 @@ login account is required. Run them with: ## Read/Write Functional Tests. -Read/Write functional tests use partner-bugzilla.redhat.com, which is a +Read/Write functional tests use bugzilla.stage.redhat.com, which is a bugzilla instance specifically for this type of testing. Data is occasionally hard synced with regular bugzilla.redhat.com, and all local edits are removed. Login accounts are also synced. If you want access to -partner-bugzilla.redhat.com, sign up for a regular bugzilla.redhat.com login +bugzilla.stage.redhat.com, sign up for a regular bugzilla.redhat.com login and wait for the next sync period. Before running these tests, you'll need to cache login credentials. Example: - ./bugzilla-cli --bugzilla=partner-bugzilla.redhat.com --username=$USER login + ./bugzilla-cli --bugzilla=bugzilla.stage.redhat.com --username=$USER login pytest --rw-functional ## Testing across python versions diff --git a/NEWS.md b/NEWS.md index 78190215..f3c91ab1 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,38 @@ # python-bugzilla release news +## Release 3.3.0 (June, 2024) +- Expose error codes from the REST API (Stanislav Levin) +- Fixed broken link in documentation (Danilo C. L. de Paula) +- Set `Bug.weburl` that is compatible with the REST API +- Do not convert 'blocks' or 'depends' to int in `Bugzilla.build_update` (Adam Williamson) +- Use proper REST API route for getting a single bug +- Avoid duplicate entries when one id is 0 (Ricardo Branco) +- Removed unused argument from `Bugzilla.add_dict` +- Fixed API key leak (Ricardo Branco) +- Automatically include alias in include_fields in `Bugzilla._getbugs` +- Added method `Bugzilla.query_return_extra` +- cli: Support --field and --field-json for bugzilla attach + +## Release 3.2.0 (January 12, 2022) +- Use soon-to-be-required Authorization header for RH bugzilla +- Remove cookie auth support + +## Release 3.1.0 (July 27, 2021) +- Detect bugzilla.stage.redhat.com as RHBugzilla +- Add limit as option to build_query (Ivan Lausuch) + +## Release 3.0.2 (November 12, 2020) +- Fix API key leaking into requests exceptions + +## Release 3.0.1 (October 07, 2020) +- Skip man page generation to fix build on Windows (Alexander Todorov) + +## Release 3.0.0 (October 03, 2020) +- Drop python2 support +- New option `bugzilla modify --minor-update option` +- requests: use PYTHONBUGZILLA_REQUESTS_TIMEOUT env variable +- xmlrpc: Don't add api key to passed in user dictionary + ## Release 2.5.0 (July 04, 2020) - cli: Add query --extrafield, --includefield, --excludefield - Revive bugzilla.rhbugzilla.RHBugzilla import path diff --git a/README.md b/README.md index 5943aa3e..4c40be66 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,16 @@ [![CI](https://github.com/python-bugzilla/python-bugzilla/workflows/CI/badge.svg)](https://github.com/python-bugzilla/python-bugzilla/actions?query=workflow%3ACI) +[![codecov](https://codecov.io/gh/python-bugzilla/python-bugzilla/branch/main/graph/badge.svg?token=4R3FR4RVH4)](https://codecov.io/gh/python-bugzilla/python-bugzilla) [![PyPI](https://img.shields.io/pypi/v/python-bugzilla)](https://pypi.org/project/python-bugzilla/) # python-bugzilla This package provides two bits: -* 'bugzilla' python module for talking to a [Bugzilla](https://www.bugzilla.org/) instance over XMLRPC or REST -* /usr/bin/bugzilla command line tool for performing actions from the command line: create or edit bugs, various queries, etc. +* `bugzilla` python module for talking to a [Bugzilla](https://www.bugzilla.org/) instance over XMLRPC or REST +* `/usr/bin/bugzilla` command line tool for performing actions from the command line: create or edit bugs, various queries, etc. -This was originally written specifically for Red Hat's Bugzilla instance -and is used heavily at Red Hat and in Fedora, but it should still be +This was originally written specifically for [Red Hat's Bugzilla instance](https://bugzilla.redhat.com) +and is used heavily at Red Hat and in Fedora, but it should be generically useful. You can find some code examples in the [examples](examples) directory. diff --git a/bugzilla/_authfiles.py b/bugzilla/_authfiles.py index a2686c49..bdb977e7 100644 --- a/bugzilla/_authfiles.py +++ b/bugzilla/_authfiles.py @@ -1,12 +1,11 @@ # This work is licensed under the GNU GPLv2 or later. # See the COPYING file in the top-level directory. +import configparser import os from logging import getLogger +import urllib.parse -from ._compatimports import (ConfigParser, LoadError, - MozillaCookieJar, urlparse) -from .exceptions import BugzillaError from ._util import listify log = getLogger(__name__) @@ -15,7 +14,7 @@ def _parse_hostname(url): # If http://example.com is passed, netloc=example.com path="" # If just example.com is passed, netloc="" path=example.com - parsedbits = urlparse(url) + parsedbits = urllib.parse.urlparse(url) return parsedbits.netloc or parsedbits.path @@ -25,17 +24,12 @@ def _makedirs(path): os.makedirs(os.path.dirname(path), 0o700) -def _default_location(filename, kind): +def _default_cache_location(filename): """ - Determine default location for passed filename and xdg kind, - example: ~/.cache/python-bugzilla/bugzillacookies + Determine default location for passed xdg filename. + example: ~/.cache/python-bugzilla/bugzillarc """ - xdgpath = os.path.expanduser("~/.%s/python-bugzilla/%s" % (kind, filename)) - return xdgpath - - -def _default_cache_location(filename): - return _default_location(filename, 'cache') + return os.path.expanduser("~/.cache/python-bugzilla/%s" % filename) class _BugzillaRCFile(object): @@ -60,7 +54,7 @@ def set_configpaths(self, configpaths): configpaths = [os.path.expanduser(p) for p in listify(configpaths or [])] - cfg = ConfigParser() + cfg = configparser.ConfigParser() read_files = cfg.read(configpaths) if read_files: log.info("Found bugzillarc files: %s", read_files) @@ -118,7 +112,7 @@ def save_api_key(self, url, api_key): config_filename = configpaths[-1] section = _parse_hostname(url) - cfg = ConfigParser() + cfg = configparser.ConfigParser() cfg.read(config_filename) if section not in cfg.sections(): @@ -146,14 +140,14 @@ def __init__(self): self._cfg = None def _get_domain(self, url): - domain = urlparse(url)[1] + domain = urllib.parse.urlparse(url)[1] if domain and domain not in self._cfg.sections(): self._cfg.add_section(domain) return domain def get_value(self, url): domain = self._get_domain(url) - if self._cfg.has_option(domain, 'token'): + if domain and self._cfg.has_option(domain, 'token'): return self._cfg.get(domain, 'token') return None @@ -178,56 +172,8 @@ def get_filename(self): def set_filename(self, filename): log.debug("Using tokenfile=%s", filename) - cfg = ConfigParser() + cfg = configparser.ConfigParser() if filename: cfg.read(filename) self._filename = filename self._cfg = cfg - - -class _BugzillaCookieCache(object): - @staticmethod - def get_default_path(): - return _default_cache_location("bugzillacookies") - - def __init__(self): - self._cookiejar = None - - def _build_cookiejar(self, cookiefile): - cj = MozillaCookieJar(cookiefile) - if (cookiefile is None or - not os.path.exists(cookiefile)): - return cj - - try: - cj.load() - return cj - except LoadError: - raise BugzillaError("cookiefile=%s not in Mozilla format" % - cookiefile) - - def set_filename(self, cookiefile): - log.debug("Using cookiefile=%s", cookiefile) - self._cookiejar = self._build_cookiejar(cookiefile) - - def get_filename(self): - return self._cookiejar.filename - - def get_cookiejar(self): - return self._cookiejar - - def set_cookies(self, cookies): - for cookie in cookies: - self._cookiejar.set_cookie(cookie) - - cookiefile = self._cookiejar.filename - if not cookiefile: - return - - if not os.path.exists(cookiefile): - _makedirs(cookiefile) - # Make sure a new file has correct permissions - open(cookiefile, 'a').close() - os.chmod(cookiefile, 0o600) - - self._cookiejar.save() diff --git a/bugzilla/_backendbase.py b/bugzilla/_backendbase.py index b81e1082..8fd9a80e 100644 --- a/bugzilla/_backendbase.py +++ b/bugzilla/_backendbase.py @@ -22,7 +22,7 @@ def __init__(self, url, bugzillasession): @staticmethod def probe(url): try: - requests.head(url).raise_for_status() + requests.head(url, timeout=10).raise_for_status() return True # pragma: no cover except Exception as e: log.debug("Failed to probe url=%s : %s", url, str(e)) diff --git a/bugzilla/_backendrest.py b/bugzilla/_backendrest.py index d2090d25..45bc4999 100644 --- a/bugzilla/_backendrest.py +++ b/bugzilla/_backendrest.py @@ -7,7 +7,7 @@ import os from ._backendbase import _BackendBase -from .exceptions import BugzillaError +from .exceptions import BugzillaError, BugzillaHTTPError from ._util import listify @@ -26,41 +26,60 @@ class _BackendREST(_BackendBase): """ def __init__(self, url, bugzillasession): _BackendBase.__init__(self, url, bugzillasession) - self._bugzillasession.set_content_type("application/json") + self._bugzillasession.set_rest_defaults() ######################### # Internal REST helpers # ######################### + def _handle_error(self, e): + response = getattr(e, "response", None) + if response is None: + raise e # pragma: no cover - def _handle_response(self, response): - response.raise_for_status() - text = response.text.encode("utf-8") + if response.status_code in [400, 401, 404]: + self._handle_error_response(response.text) + raise e + def _handle_error_response(self, text): + try: + result = json.loads(text) + except json.JSONDecodeError: + return + + if result.get("error"): + raise BugzillaError(result["message"], code=result["code"]) + + def _handle_response(self, text): try: ret = dict(json.loads(text)) - except Exception: + except Exception: # pragma: no cover log.debug("Failed to parse REST response. Output is:\n%s", text) raise - if ret.get("error", False): + if ret.get("error", False): # pragma: no cover raise BugzillaError(ret["message"], code=ret["code"]) return ret - def _op(self, optype, apiurl, paramdict=None): + def _op(self, method, apiurl, paramdict=None): fullurl = os.path.join(self._url, apiurl.lstrip("/")) - log.debug("Bugzilla REST %s %s params=%s", optype, fullurl, paramdict) - session = self._bugzillasession.get_requests_session() - data = json.dumps(paramdict or {}) - - if optype == "POST": - response = session.post(fullurl, data=data) - elif optype == "PUT": - response = session.put(fullurl, data=data) + log.debug("Bugzilla REST %s %s params=%s", method, fullurl, paramdict) + + data = None + authparams = self._bugzillasession.get_auth_params() + if method == "GET": + authparams.update(paramdict or {}) else: - response = session.get(fullurl, params=paramdict) + data = json.dumps(paramdict or {}) - return self._handle_response(response) + try: + response = self._bugzillasession.request( + method, fullurl, data=data, params=authparams + ) + except BugzillaHTTPError as e: + self._handle_error(e) + + return self._handle_response(response.text) def _get(self, *args, **kwargs): return self._op("GET", *args, **kwargs) @@ -88,9 +107,23 @@ def bug_create(self, paramdict): def bug_fields(self, paramdict): return self._get("/field/bug", paramdict) def bug_get(self, bug_ids, aliases, paramdict): + bug_list = listify(bug_ids) + alias_list = listify(aliases) + permissive = paramdict.pop("permissive", False) data = paramdict.copy() - data["id"] = listify(bug_ids) - data["alias"] = listify(aliases) + + # FYI: The high-level API expects the backends to raise an exception + # when retrieval of a single bug fails (default behavior of the XMLRPC + # API), but the REST API simply returns an empty search result set. + # To ensure compliant behavior, the REST backend needs to use the + # explicit URL to get a single bug. + if not permissive and len(bug_list or []) + len(alias_list or []) == 1: + for id_list in (bug_list, alias_list): + if id_list: + return self._get("/bug/%s" % id_list[0], data) + + data["id"] = bug_list + data["alias"] = alias_list ret = self._get("/bug", data) return ret @@ -151,19 +184,19 @@ def component_create(self, paramdict): return self._post("/component", paramdict) def component_update(self, paramdict): if "ids" in paramdict: - apiurl = str(listify(paramdict["ids"])[0]) + apiurl = str(listify(paramdict["ids"])[0]) # pragma: no cover if "names" in paramdict: apiurl = ("%(product)s/%(component)s" % listify(paramdict["names"])[0]) return self._put("/component/%s" % apiurl, paramdict) - def externalbugs_add(self, paramdict): + def externalbugs_add(self, paramdict): # pragma: no cover raise BugzillaError( "No REST API available yet for externalbugs_add") - def externalbugs_remove(self, paramdict): + def externalbugs_remove(self, paramdict): # pragma: no cover raise BugzillaError( "No REST API available yet for externalbugs_remove") - def externalbugs_update(self, paramdict): + def externalbugs_update(self, paramdict): # pragma: no cover raise BugzillaError( "No REST API available yet for externalbugs_update") @@ -190,7 +223,7 @@ def user_logout(self): def user_update(self, paramdict): urlid = None if "ids" in paramdict: - urlid = listify(paramdict["ids"])[0] + urlid = listify(paramdict["ids"])[0] # pragma: no cover if "names" in paramdict: urlid = listify(paramdict["names"])[0] return self._put("/user/%s" % urlid, paramdict) diff --git a/bugzilla/_backendxmlrpc.py b/bugzilla/_backendxmlrpc.py index e2182cd7..0558b350 100644 --- a/bugzilla/_backendxmlrpc.py +++ b/bugzilla/_backendxmlrpc.py @@ -3,12 +3,12 @@ from logging import getLogger import sys +from xmlrpc.client import (Binary, Fault, ProtocolError, + ServerProxy, Transport) from requests import RequestException from ._backendbase import _BackendBase -from ._compatimports import (Binary, Fault, ProtocolError, - ServerProxy, Transport) from .exceptions import BugzillaError from ._util import listify @@ -22,7 +22,7 @@ def __init__(self, bugzillasession): Transport.__init__(self, use_datetime=False) self.__bugzillasession = bugzillasession - self.__bugzillasession.set_content_type("text/xml") + self.__bugzillasession.set_xmlrpc_defaults() self.__seen_valid_xml = False # Override Transport.user_agent @@ -39,22 +39,16 @@ def __request_helper(self, url, request_body): """ response = None # pylint: disable=try-except-raise + # pylint: disable=raise-missing-from try: - session = self.__bugzillasession.get_requests_session() - response = session.post(url, data=request_body) + response = self.__bugzillasession.request( + "POST", url, data=request_body) - # We expect utf-8 from the server - response.encoding = 'UTF-8' - - # update/set any cookies - self.__bugzillasession.set_response_cookies(response) - - response.raise_for_status() return self.parse_response(response) except RequestException as e: if not response: raise - raise ProtocolError( + raise ProtocolError( # pragma: no cover url, response.status_code, str(e), response.headers) except Fault: raise @@ -81,7 +75,7 @@ def parse_response(self, response): msg = response.text.encode('utf-8') try: parser.feed(msg) - except Exception: + except Exception: # pragma: no cover log.debug("Failed to parse this XMLRPC response:\n%s", msg) raise @@ -120,26 +114,18 @@ def _ServerProxy__request(self, methodname, params): """ Overrides ServerProxy _request method """ - if len(params) == 0: - params = ({}, ) - - log.debug("XMLRPC call: %s(%s)", methodname, params[0]) - api_key = self.__bugzillasession.get_api_key() - token_value = self.__bugzillasession.get_token_value() + # params is a singleton tuple, enforced by xmlrpc.client.dumps + newparams = params and params[0].copy() or {} - if api_key is not None: - if 'Bugzilla_api_key' not in params[0]: - params[0]['Bugzilla_api_key'] = api_key - elif token_value is not None: - if 'Bugzilla_token' not in params[0]: - params[0]['Bugzilla_token'] = token_value + log.debug("XMLRPC call: %s(%s)", methodname, newparams) + authparams = self.__bugzillasession.get_auth_params() + authparams.update(newparams) # pylint: disable=no-member - ret = ServerProxy._ServerProxy__request(self, methodname, params) + ret = ServerProxy._ServerProxy__request( + self, methodname, (authparams,)) # pylint: enable=no-member - if isinstance(ret, dict) and 'token' in ret.keys(): - self.__bugzillasession.set_token_value(ret.get('token')) return ret diff --git a/bugzilla/_cli.py b/bugzilla/_cli.py index 26a48ad2..02d6a367 100755 --- a/bugzilla/_cli.py +++ b/bugzilla/_cli.py @@ -9,8 +9,6 @@ # This work is licensed under the GNU GPLv2 or later. # See the COPYING file in the top-level directory. -from __future__ import print_function - import argparse import base64 import datetime @@ -23,12 +21,12 @@ import socket import sys import tempfile +import urllib.parse +import xmlrpc.client import requests.exceptions import bugzilla -from bugzilla._compatimports import Fault, ProtocolError, urlparse -from bugzilla._util import to_encoding DEFAULT_BZ = 'https://bugzilla.redhat.com' @@ -62,7 +60,7 @@ def open_without_clobber(name, *args): name = "%s.%i" % (orig_name, count) count += 1 else: # pragma: no cover - raise IOError(err.errno, err.strerror, err.filename) + raise IOError(err.errno, err.strerror, err.filename) from None fobj = open(name, *args) if fd != fobj.fileno(): os.close(fd) @@ -127,8 +125,7 @@ def _setup_root_parser(): help="Don't save any bugzilla cookies or tokens to disk, and " "don't use any pre-existing credentials.") - p.add_argument('--cookiefile', default=None, - help="cookie file to use for bugzilla authentication") + p.add_argument('--cookiefile', default=None, help=argparse.SUPPRESS) p.add_argument('--tokenfile', default=None, help="token file to use for bugzilla authentication") @@ -188,6 +185,19 @@ def _parser_add_output_options(p): "section 'Output options' for more details.") +def _parser_add_field_passthrough_opts(p): + p.add_argument('--field', + metavar="FIELD=VALUE", action="append", dest="fields", + help="Manually specify a bugzilla API field. FIELD is " + "the raw name used by the bugzilla instance. For example, if your " + "bugzilla instance has a custom field cf_my_field, do:\n" + " --field cf_my_field=VALUE") + p.add_argument('--field-json', + metavar="JSONSTRING", action="append", dest="field_jsons", + help="Specify --field data as a JSON string. Example: --field-json " + '\'{"cf_my_field": "VALUE", "cf_array_field": [1, 2]}\'') + + def _parser_add_bz_fields(rootp, command): cmd_new = (command == "new") cmd_query = (command == "query") @@ -234,6 +244,10 @@ def _parser_add_bz_fields(rootp, command): p.add_argument('--cc', action="append", help="CC list") p.add_argument('-a', '--assigned_to', '--assignee', help="Bug assignee") p.add_argument('-q', '--qa_contact', help='QA contact') + if cmd_modify: + p.add_argument("--minor-update", action="store_true", + help="Request bugzilla to not send any " + "email about this change") if not cmd_new: p.add_argument('-f', '--flag', action='append', @@ -255,13 +269,7 @@ def _parser_add_bz_fields(rootp, command): p.add_argument('-F', '--fixed_in', help="RHBZ 'Fixed in version' field") - # Put this at the end, so it sticks out more - p.add_argument('--field', - metavar="FIELD=VALUE", action="append", dest="fields", - help="Manually specify a bugzilla API field. FIELD is " - "the raw name used by the bugzilla instance. For example, if your " - "bugzilla instance has a custom field cf_my_field, do:\n" - " --field cf_my_field=VALUE") + _parser_add_field_passthrough_opts(p) if not cmd_modify: _parser_add_output_options(rootp) @@ -400,6 +408,8 @@ def _setup_action_attach_parser(subparsers): p.add_argument('--private', action='store_true', default=False, help='Mark new comment as private') + _parser_add_field_passthrough_opts(p) + def _setup_action_login_parser(subparsers): usage = 'bugzilla login [--api-key] [username [password]]' @@ -435,18 +445,28 @@ def setup_parser(): # Command routines # #################### -def _merge_field_opts(query, opt, parser): - # Add any custom fields if specified - if opt.fields is None: - return +def _merge_field_opts(query, fields, field_jsons, parser): + values = {} - for f in opt.fields: + # Add any custom fields if specified + for f in (fields or []): try: f, v = f.split('=', 1) - query[f] = v + values[f] = v except Exception: parser.error("Invalid field argument provided: %s" % (f)) + for j in (field_jsons or []): + try: + jvalues = json.loads(j) + values.update(jvalues) + except Exception as e: + parser.error("Invalid field-json value=%s: %s" % (j, e)) + + if values: + log.debug("parsed --field* values: %s", values) + query.update(values) + def _do_query(bz, opt, parser): q = {} @@ -526,45 +546,82 @@ def _do_query(bz, opt, parser): if include_fields is not None: include_fields.sort() - built_query = bz.build_query( - product=opt.product or None, - component=opt.component or None, - sub_component=opt.sub_component or None, - version=opt.version or None, - reporter=opt.reporter or None, - bug_id=opt.id or None, - short_desc=opt.summary or None, - long_desc=opt.comment or None, - cc=opt.cc or None, - assigned_to=opt.assigned_to or None, - qa_contact=opt.qa_contact or None, - status=opt.status or None, - blocked=opt.blocked or None, - dependson=opt.dependson or None, - keywords=opt.keywords or None, - keywords_type=opt.keywords_type or None, - url=opt.url or None, - url_type=opt.url_type or None, - status_whiteboard=opt.whiteboard or None, - status_whiteboard_type=opt.status_whiteboard_type or None, - fixed_in=opt.fixed_in or None, - fixed_in_type=opt.fixed_in_type or None, - flag=opt.flag or None, - alias=opt.alias or None, - qa_whiteboard=opt.qa_whiteboard or None, - devel_whiteboard=opt.devel_whiteboard or None, - bug_severity=opt.severity or None, - priority=opt.priority or None, - target_release=opt.target_release or None, - target_milestone=opt.target_milestone or None, - emailtype=opt.emailtype or None, - include_fields=include_fields, - quicksearch=opt.quicksearch or None, - savedsearch=opt.savedsearch or None, - savedsearch_sharer_id=opt.savedsearch_sharer_id or None, - tags=opt.tags or None) - - _merge_field_opts(built_query, opt, parser) + kwopts = {} + if opt.product: + kwopts["product"] = opt.product + if opt.component: + kwopts["component"] = opt.component + if opt.sub_component: + kwopts["sub_component"] = opt.sub_component + if opt.version: + kwopts["version"] = opt.version + if opt.reporter: + kwopts["reporter"] = opt.reporter + if opt.id: + kwopts["bug_id"] = opt.id + if opt.summary: + kwopts["short_desc"] = opt.summary + if opt.comment: + kwopts["long_desc"] = opt.comment + if opt.cc: + kwopts["cc"] = opt.cc + if opt.assigned_to: + kwopts["assigned_to"] = opt.assigned_to + if opt.qa_contact: + kwopts["qa_contact"] = opt.qa_contact + if opt.status: + kwopts["status"] = opt.status + if opt.blocked: + kwopts["blocked"] = opt.blocked + if opt.dependson: + kwopts["dependson"] = opt.dependson + if opt.keywords: + kwopts["keywords"] = opt.keywords + if opt.keywords_type: + kwopts["keywords_type"] = opt.keywords_type + if opt.url: + kwopts["url"] = opt.url + if opt.url_type: + kwopts["url_type"] = opt.url_type + if opt.whiteboard: + kwopts["status_whiteboard"] = opt.whiteboard + if opt.status_whiteboard_type: + kwopts["status_whiteboard_type"] = opt.status_whiteboard_type + if opt.fixed_in: + kwopts["fixed_in"] = opt.fixed_in + if opt.fixed_in_type: + kwopts["fixed_in_type"] = opt.fixed_in_type + if opt.flag: + kwopts["flag"] = opt.flag + if opt.alias: + kwopts["alias"] = opt.alias + if opt.qa_whiteboard: + kwopts["qa_whiteboard"] = opt.qa_whiteboard + if opt.devel_whiteboard: + kwopts["devel_whiteboard"] = opt.devel_whiteboard + if opt.severity: + kwopts["bug_severity"] = opt.severity + if opt.priority: + kwopts["priority"] = opt.priority + if opt.target_release: + kwopts["target_release"] = opt.target_release + if opt.target_milestone: + kwopts["target_milestone"] = opt.target_milestone + if opt.emailtype: + kwopts["emailtype"] = opt.emailtype + if include_fields: + kwopts["include_fields"] = include_fields + if opt.quicksearch: + kwopts["quicksearch"] = opt.quicksearch + if opt.savedsearch: + kwopts["savedsearch"] = opt.savedsearch + if opt.savedsearch_sharer_id: + kwopts["savedsearch_sharer_id"] = opt.savedsearch_sharer_id + if opt.tags: + kwopts["tags"] = opt.tags + + built_query = bz.build_query(**kwopts) + _merge_field_opts(built_query, opt.fields, opt.field_jsons, parser) built_query.update(q) q = built_query @@ -620,13 +677,12 @@ def _filter_components(compdetails): elif opt.versions: proddict = bz.getproducts()[0] for v in proddict['versions']: - print(to_encoding(v["name"])) + print(str(v["name"] or '')) elif opt.component_owners: details = bz.getcomponentsdetails(productname) for c in sorted(_filter_components(details)): - print(to_encoding(u"%s: %s" % (c, - details[c]['default_assigned_to']))) + print("%s: %s" % (c, details[c]['default_assigned_to'])) def _convert_to_outputformat(output): @@ -691,8 +747,7 @@ def _format_output_raw(buglist): continue if attrname.startswith("_"): continue - print(to_encoding(u"ATTRIBUTE[%s]: %s" % - (attrname, b.__dict__[attrname]))) + print("ATTRIBUTE[%s]: %s" % (attrname, b.__dict__[attrname])) print("\n\n") @@ -758,7 +813,7 @@ def _bug_field_repl_cb(bz, b, matchobj): val = getattr(b, fieldname, "") vallist = isinstance(val, list) and val or [val] - val = ','.join([to_encoding(v) for v in vallist]) + val = ','.join([str(v or '') for v in vallist]) return val @@ -828,31 +883,52 @@ def parse_multi(val): return _parse_triset(val, checkplus=False, checkminus=False, checkequal=False, splitcomma=True)[0] - ret = bz.build_createbug( - blocks=parse_multi(opt.blocked) or None, - cc=parse_multi(opt.cc) or None, - component=opt.component or None, - depends_on=parse_multi(opt.dependson) or None, - description=opt.comment or None, - groups=parse_multi(opt.groups) or None, - keywords=parse_multi(opt.keywords) or None, - op_sys=opt.os or None, - platform=opt.arch or None, - priority=opt.priority or None, - product=opt.product or None, - severity=opt.severity or None, - summary=opt.summary or None, - url=opt.url or None, - version=opt.version or None, - assigned_to=opt.assigned_to or None, - qa_contact=opt.qa_contact or None, - sub_component=opt.sub_component or None, - alias=opt.alias or None, - comment_tags=opt.comment_tag or None, - comment_private=opt.private or None, - ) - - _merge_field_opts(ret, opt, parser) + kwopts = {} + if opt.blocked: + kwopts["blocks"] = parse_multi(opt.blocked) + if opt.cc: + kwopts["cc"] = parse_multi(opt.cc) + if opt.component: + kwopts["component"] = opt.component + if opt.dependson: + kwopts["depends_on"] = parse_multi(opt.dependson) + if opt.comment: + kwopts["description"] = opt.comment + if opt.groups: + kwopts["groups"] = parse_multi(opt.groups) + if opt.keywords: + kwopts["keywords"] = parse_multi(opt.keywords) + if opt.os: + kwopts["op_sys"] = opt.os + if opt.arch: + kwopts["platform"] = opt.arch + if opt.priority: + kwopts["priority"] = opt.priority + if opt.product: + kwopts["product"] = opt.product + if opt.severity: + kwopts["severity"] = opt.severity + if opt.summary: + kwopts["summary"] = opt.summary + if opt.url: + kwopts["url"] = opt.url + if opt.version: + kwopts["version"] = opt.version + if opt.assigned_to: + kwopts["assigned_to"] = opt.assigned_to + if opt.qa_contact: + kwopts["qa_contact"] = opt.qa_contact + if opt.sub_component: + kwopts["sub_component"] = opt.sub_component + if opt.alias: + kwopts["alias"] = opt.alias + if opt.comment_tag: + kwopts["comment_tags"] = opt.comment_tag + if opt.private: + kwopts["comment_private"] = opt.private + + ret = bz.build_createbug(**kwopts) + _merge_field_opts(ret, opt.fields, opt.field_jsons, parser) b = bz.createbug(ret) b.refresh() @@ -890,50 +966,96 @@ def _do_modify(bz, parser, opt): for f in opt.flag: flags.append({"name": f[:-1], "status": f[-1]}) - update = bz.build_update( - assigned_to=opt.assigned_to or None, - comment=opt.comment or None, - comment_private=opt.private or None, - component=opt.component or None, - product=opt.product or None, - blocks_add=add_blk or None, - blocks_remove=rm_blk or None, - blocks_set=set_blk, - url=opt.url or None, - cc_add=add_cc or None, - cc_remove=rm_cc or None, - depends_on_add=add_deps or None, - depends_on_remove=rm_deps or None, - depends_on_set=set_deps, - groups_add=add_groups or None, - groups_remove=rm_groups or None, - keywords_add=add_key or None, - keywords_remove=rm_key or None, - keywords_set=set_key, - op_sys=opt.os or None, - platform=opt.arch or None, - priority=opt.priority or None, - qa_contact=opt.qa_contact or None, - severity=opt.severity or None, - status=status, - summary=opt.summary or None, - version=opt.version or None, - reset_assigned_to=opt.reset_assignee or None, - reset_qa_contact=opt.reset_qa_contact or None, - resolution=opt.close or None, - target_release=opt.target_release or None, - target_milestone=opt.target_milestone or None, - dupe_of=opt.dupeid or None, - fixed_in=opt.fixed_in or None, - whiteboard=set_wb and set_wb[0] or None, - devel_whiteboard=set_devwb and set_devwb[0] or None, - internal_whiteboard=set_intwb and set_intwb[0] or None, - qa_whiteboard=set_qawb and set_qawb[0] or None, - sub_component=opt.sub_component or None, - alias=opt.alias or None, - flags=flags or None, - comment_tags=opt.comment_tag or None, - ) + update_opts = {} + + if opt.assigned_to: + update_opts["assigned_to"] = opt.assigned_to + if opt.comment: + update_opts["comment"] = opt.comment + if opt.private: + update_opts["comment_private"] = opt.private + if opt.component: + update_opts["component"] = opt.component + if opt.product: + update_opts["product"] = opt.product + if add_blk: + update_opts["blocks_add"] = add_blk + if rm_blk: + update_opts["blocks_remove"] = rm_blk + if set_blk is not None: + update_opts["blocks_set"] = set_blk + if opt.url: + update_opts["url"] = opt.url + if add_cc: + update_opts["cc_add"] = add_cc + if rm_cc: + update_opts["cc_remove"] = rm_cc + if add_deps: + update_opts["depends_on_add"] = add_deps + if rm_deps: + update_opts["depends_on_remove"] = rm_deps + if set_deps is not None: + update_opts["depends_on_set"] = set_deps + if add_groups: + update_opts["groups_add"] = add_groups + if rm_groups: + update_opts["groups_remove"] = rm_groups + if add_key: + update_opts["keywords_add"] = add_key + if rm_key: + update_opts["keywords_remove"] = rm_key + if set_key is not None: + update_opts["keywords_set"] = set_key + if opt.os: + update_opts["op_sys"] = opt.os + if opt.arch: + update_opts["platform"] = opt.arch + if opt.priority: + update_opts["priority"] = opt.priority + if opt.qa_contact: + update_opts["qa_contact"] = opt.qa_contact + if opt.severity: + update_opts["severity"] = opt.severity + if status: + update_opts["status"] = status + if opt.summary: + update_opts["summary"] = opt.summary + if opt.version: + update_opts["version"] = opt.version + if opt.reset_assignee: + update_opts["reset_assigned_to"] = opt.reset_assignee + if opt.reset_qa_contact: + update_opts["reset_qa_contact"] = opt.reset_qa_contact + if opt.close: + update_opts["resolution"] = opt.close + if opt.target_release: + update_opts["target_release"] = opt.target_release + if opt.target_milestone: + update_opts["target_milestone"] = opt.target_milestone + if opt.dupeid: + update_opts["dupe_of"] = opt.dupeid + if opt.fixed_in: + update_opts["fixed_in"] = opt.fixed_in + if set_wb and set_wb[0]: + update_opts["whiteboard"] = set_wb and set_wb[0] + if set_devwb and set_devwb[0]: + update_opts["devel_whiteboard"] = set_devwb and set_devwb[0] + if set_intwb and set_intwb[0]: + update_opts["internal_whiteboard"] = set_intwb and set_intwb[0] + if set_qawb and set_qawb[0]: + update_opts["qa_whiteboard"] = set_qawb and set_qawb[0] + if opt.sub_component: + update_opts["sub_component"] = opt.sub_component + if opt.alias: + update_opts["alias"] = opt.alias + if flags: + update_opts["flags"] = flags + if opt.comment_tag: + update_opts["comment_tags"] = opt.comment_tag + if opt.minor_update: + update_opts["minor_update"] = opt.minor_update + + update = bz.build_update(**update_opts) # We make this a little convoluted to facilitate unit testing wbmap = { @@ -945,9 +1067,9 @@ def _do_modify(bz, parser, opt): for k, v in wbmap.copy().items(): if not v[0] and not v[1]: - del(wbmap[k]) + del wbmap[k] - _merge_field_opts(update, opt, parser) + _merge_field_opts(update, opt.fields, opt.field_jsons, parser) log.debug("update bug dict=%s", update) log.debug("update whiteboard dict=%s", wbmap) @@ -1055,6 +1177,8 @@ def _do_set_attach(bz, opt, parser): kwargs["is_private"] = True desc = opt.desc or os.path.basename(fileobj.name) + _merge_field_opts(kwargs, opt.fields, opt.field_jsons, parser) + # Upload attachments for bugid in opt.ids: attid = bz.attachfile(bugid, fileobj, desc, **kwargs) @@ -1109,7 +1233,7 @@ def _handle_login(opt, action, bz): print("You already have an API key configured for %s" % bz.url) print("There is no need to cache a login token. Exiting.") sys.exit(0) - print("Logging into %s" % urlparse(bz.url)[1]) + print("Logging into %s" % urllib.parse.urlparse(bz.url)[1]) bz.interactive_login(username, password, restrict_login=opt.restrict_login) except bugzilla.BugzillaError as e: @@ -1190,7 +1314,7 @@ def main(unittest_bz_instance=None): except KeyboardInterrupt: print("\nExited at user request.") sys.exit(1) - except (Fault, bugzilla.BugzillaError) as e: + except (xmlrpc.client.Fault, bugzilla.BugzillaError) as e: print("\nServer error: %s" % str(e)) sys.exit(3) except requests.exceptions.SSLError as e: @@ -1204,7 +1328,7 @@ def main(unittest_bz_instance=None): requests.exceptions.HTTPError, requests.exceptions.ConnectionError, requests.exceptions.InvalidURL, - ProtocolError) as e: + xmlrpc.client.ProtocolError) as e: print("\nConnection lost/failed: %s" % str(e)) sys.exit(2) diff --git a/bugzilla/_compatimports.py b/bugzilla/_compatimports.py deleted file mode 100644 index b531566d..00000000 --- a/bugzilla/_compatimports.py +++ /dev/null @@ -1,24 +0,0 @@ -# This work is licensed under the GNU GPLv2 or later. -# See the COPYING file in the top-level directory. - -import sys - -IS_PY3 = sys.version_info[0] >= 3 - -# pylint: disable=import-error,unused-import,ungrouped-imports -# pylint: disable=no-name-in-module -if IS_PY3: - from collections.abc import Mapping - from configparser import ConfigParser - from http.cookiejar import LoadError, MozillaCookieJar - from urllib.parse import urlparse, urlunparse, parse_qsl - from xmlrpc.client import (Binary, DateTime, Fault, ProtocolError, - ServerProxy, Transport) -else: # pragma: no cover - from collections import Mapping - from ConfigParser import SafeConfigParser as ConfigParser - from cookielib import LoadError, MozillaCookieJar - from urlparse import urlparse - from xmlrpclib import (Binary, DateTime, Fault, ProtocolError, - ServerProxy, Transport) - from urlparse import urlparse, urlunparse, parse_qsl diff --git a/bugzilla/_session.py b/bugzilla/_session.py index 70631e66..ff00f3f5 100644 --- a/bugzilla/_session.py +++ b/bugzilla/_session.py @@ -3,10 +3,13 @@ from logging import getLogger -import requests +import os +import sys +import urllib.parse -from ._compatimports import urlparse +import requests +from .exceptions import BugzillaHTTPError log = getLogger(__name__) @@ -16,17 +19,19 @@ class _BugzillaSession(object): Class to handle the backend agnostic 'requests' setup """ def __init__(self, url, user_agent, - cookiecache, sslverify, cert, - tokencache, api_key, requests_session=None): + sslverify, cert, tokencache, api_key, + is_redhat_bugzilla, + requests_session=None): self._url = url self._user_agent = user_agent - self._scheme = urlparse(url)[0] - self._cookiecache = cookiecache + self._scheme = urllib.parse.urlparse(url)[0] self._tokencache = tokencache self._api_key = api_key + self._is_xmlrpc = False + self._use_auth_bearer = False if self._scheme not in ["http", "https"]: - raise Exception("Invalid URL scheme: %s (%s)" % ( + raise ValueError("Invalid URL scheme: %s (%s)" % ( self._scheme, url)) self._session = requests_session @@ -35,43 +40,82 @@ def __init__(self, url, user_agent, if cert: self._session.cert = cert - if self._cookiecache: - self._session.cookies = self._cookiecache.get_cookiejar() if sslverify is False: self._session.verify = False self._session.headers["User-Agent"] = self._user_agent - self._session.params["Bugzilla_api_key"] = self._api_key - self._set_tokencache_param() + + if is_redhat_bugzilla and self._api_key: + self._use_auth_bearer = True + self._session.headers["Authorization"] = ( + "Bearer %s" % self._api_key) + + def _get_timeout(self): + # Default to 5 minutes. This is longer than bugzilla.redhat.com's + # apparent 3 minute timeout so shouldn't affect legitimate usage, + # but saves us from indefinite hangs + DEFAULT_TIMEOUT = 300 + envtimeout = os.environ.get("PYTHONBUGZILLA_REQUESTS_TIMEOUT") + return float(envtimeout or DEFAULT_TIMEOUT) + + def set_rest_defaults(self): + self._session.headers["Content-Type"] = "application/json" + def set_xmlrpc_defaults(self): + self._is_xmlrpc = True + self._session.headers["Content-Type"] = "text/xml" def get_user_agent(self): return self._user_agent def get_scheme(self): return self._scheme - def get_api_key(self): - return self._api_key - def get_token_value(self): - return self._tokencache.get_value(self._url) - def set_token_value(self, value): - self._tokencache.set_value(self._url, value) - self._set_tokencache_param() - def set_content_type(self, value): - self._session.headers["Content-Type"] = value - - def _set_tokencache_param(self): + + def get_auth_params(self): + # bugzilla.redhat.com will error if there's auth bits in params + # when Authorization header is used + if self._use_auth_bearer: + return {} + + # Don't add a token to the params list if an API key is set. + # Keeping API key solo means bugzilla will definitely fail + # if the key expires. Passing in a token could hide that + # fact, which could make it confusing to pinpoint the issue. if self._api_key: - # Don't add a token to the params list if an API key is set. - # Keeping API key solo means bugzilla will definitely fail - # if the key expires. Passing in a token could hide that - # fact, which could make it confusing to pinpoint the issue. - return - token = self.get_token_value() - self._session.params["Bugzilla_token"] = token - - def set_response_cookies(self, response): - """ - Save any cookies received from the passed requests response - """ - self._cookiecache.set_cookies(response.cookies) + # Bugzilla 5.0 only supports api_key as a query parameter. + # Bugzilla 5.1+ takes it as a X-BUGZILLA-API-KEY header as well, + # with query param taking preference. + return {"Bugzilla_api_key": self._api_key} + + token = self._tokencache.get_value(self._url) + if token: + return {"Bugzilla_token": token} + + return {} def get_requests_session(self): return self._session + + def request(self, *args, **kwargs): + timeout = self._get_timeout() + if "timeout" not in kwargs: + kwargs["timeout"] = timeout + + try: + response = self._session.request(*args, **kwargs) + + if self._is_xmlrpc: + # This still appears to matter for properly decoding unicode + # code points in bugzilla.redhat.com content + response.encoding = "UTF-8" + + response.raise_for_status() + except Exception as e: + # Scrape the api key out of the returned exception string + message = str(e).replace(self._api_key or "", "") + if isinstance(e, requests.HTTPError): + response = getattr(e, "response", None) + raise BugzillaHTTPError( + message, response=response).with_traceback( + sys.exc_info()[2]) + raise type(e)(message).with_traceback(sys.exc_info()[2]) + + + return response diff --git a/bugzilla/_util.py b/bugzilla/_util.py index 8c137c16..04555779 100644 --- a/bugzilla/_util.py +++ b/bugzilla/_util.py @@ -1,10 +1,6 @@ # This work is licensed under the GNU GPLv2 or later. # See the COPYING file in the top-level directory. -import locale - -from ._compatimports import IS_PY3 - def listify(val): """Ensure that value is either None or a list, converting single values @@ -14,20 +10,3 @@ def listify(val): if isinstance(val, list): return val return [val] - - -def to_encoding(ustring): - """ - Locale specific printing per python version - """ - # pylint: disable=undefined-variable - - ustring = ustring or '' - if IS_PY3: - return str(ustring) - else: # pragma: no cover - strtype = basestring # noqa - string = ustring - if not isinstance(ustring, strtype): - string = str(ustring) - return string.encode(locale.getpreferredencoding(), 'replace') diff --git a/bugzilla/apiversion.py b/bugzilla/apiversion.py index 6ff41ed4..c4c11d14 100644 --- a/bugzilla/apiversion.py +++ b/bugzilla/apiversion.py @@ -4,5 +4,5 @@ # This work is licensed under the GNU GPLv2 or later. # See the COPYING file in the top-level directory. -version = "2.5.0" +version = "3.3.0" __version__ = version diff --git a/bugzilla/base.py b/bugzilla/base.py index 362e4c32..ddda9137 100644 --- a/bugzilla/base.py +++ b/bugzilla/base.py @@ -6,21 +6,21 @@ # This work is licensed under the GNU GPLv2 or later. # See the COPYING file in the top-level directory. +import collections import getpass import locale from logging import getLogger import mimetypes import os import sys +import urllib.parse from io import BytesIO -from ._authfiles import (_BugzillaRCFile, - _BugzillaCookieCache, _BugzillaTokenCache) +from ._authfiles import _BugzillaRCFile, _BugzillaTokenCache from .apiversion import __version__ from ._backendrest import _BackendREST from ._backendxmlrpc import _BackendXMLRPC -from ._compatimports import Mapping, urlparse, urlunparse, parse_qsl from .bug import Bug, Group, User from .exceptions import BugzillaError from ._rhconverters import _RHBugzillaConverters @@ -34,7 +34,7 @@ def _nested_update(d, u): # Helper for nested dict update() for k, v in list(u.items()): - if isinstance(v, Mapping): + if isinstance(v, collections.abc.Mapping): d[k] = _nested_update(d.get(k, {}), v) else: d[k] = v @@ -86,7 +86,7 @@ class Bugzilla(object): bzapi = Bugzilla("http://bugzilla.example.com") If you have previously logged into that URL, and have cached login - cookies/tokens, you will automatically be logged in. Otherwise to + tokens, you will automatically be logged in. Otherwise to log in, you can either pass auth options to __init__, or call a login helper like interactive_login(). @@ -110,13 +110,13 @@ def url_to_query(url): # pylint: disable=unpacking-non-sequence (ignore1, ignore2, path, - ignore, query, ignore3) = urlparse(url) + ignore, query, ignore3) = urllib.parse.urlparse(url) base = os.path.basename(path) if base not in ('buglist.cgi', 'query.cgi'): return {} - for (k, v) in parse_qsl(query): + for (k, v) in urllib.parse.parse_qsl(query): if k not in q: q[k] = v elif isinstance(q[k], list): @@ -141,7 +141,8 @@ def fix_url(url, force_rest=False): :param force_rest: If True, generate a REST API url """ - scheme, netloc, path, params, query, fragment = urlparse(url) + (scheme, netloc, path, + params, query, fragment) = urllib.parse.urlparse(url) if not scheme: scheme = 'https' @@ -154,7 +155,11 @@ def fix_url(url, force_rest=False): if force_rest: path = "rest/" - newurl = urlunparse((scheme, netloc, path, params, query, fragment)) + if not path.startswith("/"): + path = "/" + path + + newurl = urllib.parse.urlunparse( + (scheme, netloc, path, params, query, fragment)) return newurl @staticmethod @@ -182,18 +187,14 @@ def __init__(self, url=-1, user=None, password=None, cookiefile=-1, :param password: optional password for the connecting user :param cert: optional certificate file for client side certificate authentication - :param cookiefile: Location to cache the login session cookies so you - don't have to keep specifying username/password. Bugzilla 5+ will - use tokens instead of cookies. - If -1, use the default path. If None, don't use or save - any cookiefile. + :param cookiefile: Deprecated, raises an error if not -1 or None :param sslverify: Set this to False to skip SSL hostname and CA validation checks, like out of date certificate :param tokenfile: Location to cache the API login token so youi don't have to keep specifying username/password. If -1, use the default path. If None, don't use or save any tokenfile. - :param use_creds: If False, this disables cookiefile, tokenfile, + :param use_creds: If False, this disables tokenfile and configpaths by default. This is a convenience option to unset those values at init time. If those values are later changed, they may be used for future operations. @@ -229,25 +230,23 @@ def __init__(self, url=-1, user=None, password=None, cookiefile=-1, self._is_redhat_bugzilla = False self._rcfile = _BugzillaRCFile() - self._cookiecache = _BugzillaCookieCache() self._tokencache = _BugzillaTokenCache() self._force_rest = force_rest self._force_xmlrpc = force_xmlrpc + if cookiefile not in [None, -1]: + raise TypeError("cookiefile is deprecated, don't pass any value.") + if not use_creds: - cookiefile = None tokenfile = None configpaths = [] - if cookiefile == -1: - cookiefile = self._cookiecache.get_default_path() if tokenfile == -1: tokenfile = self._tokencache.get_default_path() if configpaths == -1: configpaths = _BugzillaRCFile.get_default_configpaths() - self._setcookiefile(cookiefile) self._settokenfile(tokenfile) self._setconfigpath(configpaths) @@ -258,8 +257,9 @@ def _detect_is_redhat_bugzilla(self): if self._is_redhat_bugzilla: return True - if "bugzilla.redhat.com" in self.url: - log.info("Using RHBugzilla for URL containing bugzilla.redhat.com") + match = ".redhat.com" + if match in self.url: + log.info("Using RHBugzilla for URL containing %s", match) return True return False @@ -270,9 +270,15 @@ def _init_class_from_url(self): """ from .oldclasses import RHBugzilla # pylint: disable=cyclic-import - if self._detect_is_redhat_bugzilla(): + if not self._detect_is_redhat_bugzilla(): + return + + self._is_redhat_bugzilla = True + if self.__class__ == Bugzilla: + # Overriding the class doesn't have any functional effect, + # but we continue to do it for API back compat incase anyone + # is doing any class comparison. We should drop this in the future self.__class__ = RHBugzilla - self._is_redhat_bugzilla = True def _get_field_aliases(self): # List of field aliases. Maps old style RHBZ parameter @@ -358,12 +364,8 @@ def _get_api_aliases(self): ################# def _getcookiefile(self): - return self._cookiecache.get_filename() - def _delcookiefile(self): - self._setcookiefile(None) - def _setcookiefile(self, cookiefile): - self._cookiecache.set_filename(cookiefile) - cookiefile = property(_getcookiefile, _setcookiefile, _delcookiefile) + return None + cookiefile = property(_getcookiefile) def _gettokenfile(self): return self._tokencache.get_filename() @@ -505,12 +507,15 @@ def connect(self, url=None): # we've changed URLs - reload config self.readconfig(overwrite=False) + # Detect if connecting to redhat bugzilla + self._init_class_from_url() + self._session = _BugzillaSession(self.url, self.user_agent, - cookiecache=self._cookiecache, sslverify=self._sslverify, cert=self.cert, tokencache=self._tokencache, api_key=self.api_key, + is_redhat_bugzilla=self._is_redhat_bugzilla, requests_session=self._user_requests_session) self._backend = backendclass(self.url, self._session) @@ -524,7 +529,6 @@ def connect(self, url=None): version = self._backend.bugzilla_version()["version"] log.debug("Bugzilla version string: %s", version) self._set_bz_version(version) - self._init_class_from_url() @property @@ -608,11 +612,13 @@ def login(self, user=None, password=None, restrict_login=None): ret = self._backend.user_login(payload) self.password = '' log.info("login succeeded for user=%s", self.user) + if "token" in ret: + self._tokencache.set_value(self.url, ret["token"]) return ret except Exception as e: log.debug("Login exception: %s", str(e), exc_info=True) raise BugzillaError("Login failed: %s" % - BugzillaError.get_bugzilla_error_string(e)) + BugzillaError.get_bugzilla_error_string(e)) from None def interactive_save_api_key(self): """ @@ -665,17 +671,23 @@ def interactive_login(self, user=None, password=None, force=False, log.info('Logging in... ') out = self.login(user, password, restrict_login) msg = "Login successful." - if "token" in out and self.tokenfile: - msg += " Token cache saved to %s" % self.tokenfile + if "token" not in out: + msg += " However no token was returned." + else: + if not self.tokenfile: + msg += " Token not saved to disk." + else: + msg += " Token cache saved to %s" % self.tokenfile if self._get_version() >= 5.0: msg += "\nToken usage is deprecated. " - msg += "Consider using bugzilla API keys instead." + msg += "Consider using bugzilla API keys instead. " + msg += "See `man bugzilla` for more details." print(msg) def logout(self): """ Log out of bugzilla. Drops server connection and user info, and - destroys authentication cookies. + destroys authentication cache """ self._backend.user_logout() self.disconnect() @@ -1072,6 +1084,11 @@ def _alias_or_int(_v): else: ids.append(idstr) + if (include_fields is not None and aliases + and "alias" not in include_fields): + # Extra field to prevent sorting (see below) from causing an error + include_fields.append("alias") + extra_fields = listify(extra_fields or []) extra_fields += self._getbug_extra_fields() @@ -1090,7 +1107,7 @@ def _alias_or_int(_v): for idval in idlist: idint, alias = _alias_or_int(idval) for bugdict in r["bugs"]: - if idint and idint != bugdict.get("id", None): + if idint is not None and idint != bugdict.get("id", None): continue aliaslist = listify(bugdict.get("alias", None) or []) if alias and alias not in aliaslist: @@ -1187,7 +1204,9 @@ def build_query(self, sub_component=None, tags=None, exclude_fields=None, - extra_fields=None): + extra_fields=None, + limit=None, + resolution=None): """ Build a query string from passed arguments. Will handle query parameter differences between various bugzilla versions. @@ -1220,6 +1239,8 @@ def build_query(self, "quicksearch": quicksearch, "savedsearch": savedsearch, "sharer_id": savedsearch_sharer_id, + "limit": limit, + "resolution": resolution, # RH extensions... don't add any more. See comment below "sub_components": listify(sub_component), @@ -1291,18 +1312,19 @@ def add_email(key, value, count): # Strip out None elements in the dict for k, v in query.copy().items(): if v is None: - del(query[k]) + del query[k] self.pre_translation(query) return query - def query(self, query): + + def query_return_extra(self, query): """ - Query bugzilla and return a list of matching bugs. - query must be a dict with fields like those in in querydata['fields']. - Returns a list of Bug objects. - Also see the _query() method for details about the underlying - implementation. + Same as `query()`, but the return value is altered to be + (buglist, values), where `values` is raw dictionary output from + the API call, excluding the bug content. For example this may + include a `limit` value if the bugzilla instance puts an implied + limit on returned result numbers. """ try: r = self._backend.bug_search(query) @@ -1316,11 +1338,25 @@ def query(self, query): raise raise BugzillaError("%s\nYour bugzilla instance does not " "appear to support API queries derived from bugzilla " - "web URL queries." % e) + "web URL queries." % e) from None + + rawbugs = r.pop("bugs") + log.debug("Query returned %s bugs", len(rawbugs)) + bugs = [Bug(self, dict=b, + autorefresh=self.bug_autorefresh) for b in rawbugs] - log.debug("Query returned %s bugs", len(r['bugs'])) - return [Bug(self, dict=b, - autorefresh=self.bug_autorefresh) for b in r['bugs']] + return bugs, r + + def query(self, query): + """ + Pass search terms to bugzilla and and return a list of matching + Bug objects. + + See `build_query` for more details about constructing the + `query` dict parameter. + """ + bugs, dummy = self.query_return_extra(query) + return bugs def pre_translation(self, query): """ @@ -1438,7 +1474,8 @@ def build_update(self, internal_whiteboard=None, sub_component=None, flags=None, - comment_tags=None): + comment_tags=None, + minor_update=None): """ Returns a python dict() with properly formatted parameters to pass to update_bugs(). See bugzilla documentation for the format @@ -1477,23 +1514,17 @@ def s(key, val, convert=None): val = convert(val) ret[key] = val - def add_dict(key, add, remove, _set=None, convert=None): + def add_dict(key, add, remove, _set=None): if add is remove is _set is None: return - def c(val): - val = listify(val) - if convert: - val = [convert(v) for v in val] - return val - newdict = {} if add is not None: - newdict["add"] = c(add) + newdict["add"] = listify(add) if remove is not None: - newdict["remove"] = c(remove) + newdict["remove"] = listify(remove) if _set is not None: - newdict["set"] = c(_set) + newdict["set"] = listify(_set) ret[key] = newdict @@ -1525,11 +1556,10 @@ def c(val): s("work_time", work_time, float) s("flags", flags) s("comment_tags", comment_tags, listify) + s("minor_update", minor_update, bool) - add_dict("blocks", blocks_add, blocks_remove, blocks_set, - convert=int) - add_dict("depends_on", depends_on_add, depends_on_remove, - depends_on_set, convert=int) + add_dict("blocks", blocks_add, blocks_remove, blocks_set) + add_dict("depends_on", depends_on_add, depends_on_remove, depends_on_set) add_dict("cc", cc_add, cc_remove) add_dict("groups", groups_add, groups_remove) add_dict("keywords", keywords_add, keywords_remove, keywords_set) @@ -1736,7 +1766,7 @@ def build_createbug(self, localdict["cc"] = listify(cc) if depends_on: localdict["depends_on"] = listify(depends_on) - if groups: + if groups is not None: localdict["groups"] = listify(groups) if keywords: localdict["keywords"] = listify(keywords) @@ -1786,7 +1816,7 @@ def _validate_createbug(self, *args, **kwargs): # Back compat handling for check_args if "check_args" in data: - del(data["check_args"]) + del data["check_args"] return data @@ -1976,7 +2006,7 @@ def add_external_tracker(self, bug_ids, ext_bz_bug_id, ext_type_id=None, ExternalBugs::WebService::add_external_bug method. This is documented at - https://bugzilla.redhat.com/docs/en/html/api/extensions/ExternalBugs/lib/WebService.html#add_external_bug + https://bugzilla.redhat.com/docs/en/html/integrating/api/Bugzilla/Extension/ExternalBugs/WebService.html#add-external-bug bug_ids: A single bug id or list of bug ids to have external trackers added. @@ -2019,7 +2049,7 @@ def update_external_tracker(self, ids=None, ext_type_id=None, ExternalBugs::WebService::update_external_bug method. This is documented at - https://bugzilla.redhat.com/docs/en/html/api/extensions/ExternalBugs/lib/WebService.html#update_external_bug + https://bugzilla.redhat.com/docs/en/html/integrating/api/Bugzilla/Extension/ExternalBugs/WebService.html#update-external-bug ids: A single external tracker bug id or list of external tracker bug ids. @@ -2064,7 +2094,7 @@ def remove_external_tracker(self, ids=None, ext_type_id=None, ExternalBugs::WebService::remove_external_bug method. This is documented at - https://bugzilla.redhat.com/docs/en/html/api/extensions/ExternalBugs/lib/WebService.html#remove_external_bug + https://bugzilla.redhat.com/docs/en/html/integrating/api/Bugzilla/Extension/ExternalBugs/WebService.html#remove-external-bug ids: A single external tracker bug id or list of external tracker bug ids. diff --git a/bugzilla/bug.py b/bugzilla/bug.py index f25609dd..15cea004 100644 --- a/bugzilla/bug.py +++ b/bugzilla/bug.py @@ -1,5 +1,3 @@ -# base.py - the base classes etc. for a Python interface to bugzilla -# # Copyright (C) 2007, 2008, 2009, 2010 Red Hat Inc. # Author: Will Woods # @@ -8,8 +6,7 @@ import copy from logging import getLogger - -from ._util import to_encoding +from urllib.parse import urlparse, urlunparse log = getLogger(__name__) @@ -43,23 +40,27 @@ def __init__(self, bugzilla, bug_id=None, dict=None, autorefresh=False): dict["id"] = bug_id self._update_dict(dict) - self.weburl = bugzilla.url.replace('xmlrpc.cgi', - 'show_bug.cgi?id=%i' % self.bug_id) + self.weburl = self._generate_weburl() + + def _generate_weburl(self): + """ + Generate the URL to the bug in the web UI + """ + parsed = urlparse(self.bugzilla.url) + return urlunparse((parsed.scheme, parsed.netloc, + '/show_bug.cgi', '', 'id=%s' % self.bug_id, + '')) def __str__(self): """ Return a simple string representation of this bug - - This is available only for compatibility. Using 'str(bug)' and - 'print(bug)' is not recommended because of potential encoding issues. - Please use unicode(bug) where possible. """ - return to_encoding(self.__unicode__()) + return self.__unicode__() def __unicode__(self): """ Return a simple unicode string representation of this bug - """ + """ return "#%-6s %-10s - %s - %s" % (self.bug_id, self.bug_status, self.assigned_to, self.summary) @@ -144,7 +145,7 @@ def _translate_dict(self, newdict): "d[%s]=%s and d[%s]=%s , dropping the value " "d[%s]", newname, newdict[newname], oldname, newdict[oldname], oldname) - del(newdict[oldname]) + del newdict[oldname] def _update_dict(self, newdict): @@ -291,14 +292,14 @@ def addcomment(self, comment, private=False): return self.bugzilla.update_bugs(self.bug_id, vals) - def getcomments(self): + def get_comments(self): """ Returns an array of comment dictionaries for this bug """ comment_list = self.bugzilla.get_comments([self.bug_id]) return comment_list['bugs'][str(self.bug_id)]['comments'] - + getcomments = get_comments ##################### # Get/Set bug flags # ##################### @@ -359,6 +360,22 @@ def updateflags(self, flags): self.bugzilla.build_update(flags=flaglist)) + ####################### + # Bug fields handling # + ####################### + + def setsummary(self, summary): + """ + Set the summary of bug to the given summary string + """ + # Create update object + vals = self.bugzilla.build_update(summary=summary) + + log.debug("setsummary: update=%s", vals) + + # Send update to bugzilla and return + return self.bugzilla.update_bugs(self.bug_id, vals) + ######################## # Experimental methods # ######################## diff --git a/bugzilla/exceptions.py b/bugzilla/exceptions.py index d884df0a..2562dc45 100644 --- a/bugzilla/exceptions.py +++ b/bugzilla/exceptions.py @@ -1,5 +1,6 @@ # This work is licensed under the GNU GPLv2 or later. # See the COPYING file in the top-level directory. +from requests import HTTPError class BugzillaError(Exception): @@ -36,3 +37,7 @@ def __init__(self, message, code=None): if self.code: message += " (code=%s)" % self.code Exception.__init__(self, message) + + +class BugzillaHTTPError(HTTPError): + """Error raised in the Bugzilla session""" diff --git a/codecov.yml b/codecov.yml index fbd9242a..4aa11c0f 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,4 +1,6 @@ -# The files aren't interesting for the unit tests run in CI +# These files will only get full coverage from running the functional +# tests, but those aren't run from CI ignore: - "bugzilla/_backendrest.py" - "bugzilla/_backendxmlrpc.py" + - "bugzilla/_session.py" diff --git a/examples/apikey.py b/examples/apikey.py index 4522129c..021d70fb 100644 --- a/examples/apikey.py +++ b/examples/apikey.py @@ -6,9 +6,6 @@ # apikey.py: Demostrate prompting for API key and passing it to Bugzilla # pylint: disable=undefined-variable -from __future__ import print_function -import sys - import bugzilla # Don't worry, changing things here is fine, and won't send any email to @@ -19,13 +16,10 @@ " https://landfill.bugzilla.org/bugzilla-5.0-branch/userprefs.cgi") print("This is a test site, so no harm will come!\n") -if sys.version_info[0] >= 3: - api_key = input("Enter Bugzilla API Key: ") # noqa -else: - api_key = raw_input("Enter Bugzilla API Key: ") # noqa +api_key = input("Enter Bugzilla API Key: ") # API key usage assumes the API caller is storing the API key; if you would # like to use one of the login options that stores credentials on-disk for -# command-line usage, use tokens or cookies. +# command-line usage, use login tokens. bzapi = bugzilla.Bugzilla(URL, api_key=api_key) assert bzapi.logged_in diff --git a/examples/bug_autorefresh.py b/examples/bug_autorefresh.py index 20f90b17..a8aa6728 100644 --- a/examples/bug_autorefresh.py +++ b/examples/bug_autorefresh.py @@ -6,12 +6,10 @@ # bug_autorefresh.py: Show what bug_autorefresh is all about, and explain # how to handle the default change via python-bugzilla in 2016 -from __future__ import print_function - import bugzilla # public test instance of bugzilla.redhat.com. It's okay to make changes -URL = "partner-bugzilla.redhat.com" +URL = "bugzilla.stage.redhat.com" bzapi = bugzilla.Bugzilla(URL) # The Bugzilla.bug_autorefresh setting controls whether bugs will diff --git a/examples/create.py b/examples/create.py index cf417fa1..124a93b0 100644 --- a/examples/create.py +++ b/examples/create.py @@ -5,8 +5,6 @@ # create.py: Create a new bug report -from __future__ import print_function - import time import bugzilla @@ -14,8 +12,8 @@ # public test instance of bugzilla.redhat.com. # # Don't worry, changing things here is fine, and won't send any email to -# users or anything. It's what partner-bugzilla.redhat.com is for! -URL = "partner-bugzilla.redhat.com" +# users or anything. It's what bugzilla.stage.redhat.com is for! +URL = "bugzilla.stage.redhat.com" bzapi = bugzilla.Bugzilla(URL) if not bzapi.logged_in: print("This example requires cached login credentials for %s" % URL) diff --git a/examples/getbug.py b/examples/getbug.py index e532a289..f164c0a1 100644 --- a/examples/getbug.py +++ b/examples/getbug.py @@ -6,21 +6,19 @@ # getbug.py: Simple demonstration of connecting to bugzilla, fetching # a bug, and printing some details. -from __future__ import print_function - import pprint import bugzilla # public test instance of bugzilla.redhat.com. It's okay to make changes -URL = "partner-bugzilla.redhat.com" +URL = "bugzilla.stage.redhat.com" bzapi = bugzilla.Bugzilla(URL) # getbug() is just a simple wrapper around getbugs(), which takes a list # IDs, if you need to fetch multiple # -# Example bug: https://partner-bugzilla.redhat.com/show_bug.cgi?id=427301 +# Example bug: https://bugzilla.stage.redhat.com/show_bug.cgi?id=427301 bug = bzapi.getbug(427301) print("Fetched bug #%s:" % bug.id) print(" Product = %s" % bug.product) @@ -35,8 +33,8 @@ # comments must be fetched separately on stock bugzilla. this just returns # a raw dict with all the info. -comments = bug.getcomments() +comments = bug.get_comments() print("\nLast comment data:\n%s" % pprint.pformat(comments[-1])) -# getcomments is just a wrapper around bzapi.get_comments(), which can be +# get_comments is just a wrapper around bzapi.get_comments(), which can be # used for bulk comments fetching diff --git a/examples/getbug_restapi.py b/examples/getbug_restapi.py index 6b1b5e29..1cb4e797 100644 --- a/examples/getbug_restapi.py +++ b/examples/getbug_restapi.py @@ -7,12 +7,10 @@ # Simple demonstration of connecting to bugzilla over the REST # API and printing some bug details. -from __future__ import print_function - import bugzilla # public test instance of bugzilla.redhat.com. It's okay to make changes -URL = "partner-bugzilla.redhat.com" +URL = "bugzilla.stage.redhat.com" # By default, if plain Bugzilla(URL) is invoked, the Bugzilla class will # attempt to determine if XMLRPC or REST API is available, with a preference diff --git a/examples/query.py b/examples/query.py index 856f64f5..05ad7026 100644 --- a/examples/query.py +++ b/examples/query.py @@ -5,14 +5,12 @@ # query.py: Perform a few varieties of queries -from __future__ import print_function - import time import bugzilla # public test instance of bugzilla.redhat.com. It's okay to make changes -URL = "partner-bugzilla.redhat.com" +URL = "bugzilla.stage.redhat.com" bzapi = bugzilla.Bugzilla(URL) @@ -57,7 +55,7 @@ # bugzilla.redhat.com, and bugzilla >= 5.0 support queries using the same # format as is used for 'advanced' search URLs via the Web UI. For example, -# I go to partner-bugzilla.redhat.com -> Search -> Advanced Search, select +# I go to bugzilla.stage.redhat.com -> Search -> Advanced Search, select # Classification=Fedora # Product=Fedora # Component=python-bugzilla @@ -67,7 +65,7 @@ # # Run that, copy the URL and bring it here, pass it to url_to_query to # convert it to a dict(), and query as usual -query = bzapi.url_to_query("https://partner-bugzilla.redhat.com/" +query = bzapi.url_to_query("https://bugzilla.stage.redhat.com/" "buglist.cgi?classification=Fedora&component=python-bugzilla&" "f1=creation_ts&o1=lessthaneq&order=Importance&product=Fedora&" "query_format=advanced&v1=2010-01-01") diff --git a/examples/redhat_query_all.py b/examples/redhat_query_all.py new file mode 100644 index 00000000..9ec5f26f --- /dev/null +++ b/examples/redhat_query_all.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +# +# This work is licensed under the GNU GPLv2 or later. +# See the COPYING file in the top-level directory. + +# redhat_query_all.py: Perform a few varieties of queries + +import bugzilla + +# public test instance of bugzilla.redhat.com. It's okay to make changes +URL = "bugzilla.stage.redhat.com" + +bzapi = bugzilla.Bugzilla(URL) + + +# In late 2021, bugzilla.redhat.com changed query() results to default to +# returning only 20 bugs. If the user passes in limit=0, that number changes +# to 1000, but is still capped if the query would return more than that. +# +# There's a discussion here with multiple proposed ways to work around it: +# https://github.com/python-bugzilla/python-bugzilla/issues/149 +# +# This method uses ids_only=True, which is a custom bugzilla.redhat.com +# query feature to bypass the query limit by only returning matching bug IDs. +# rhbz feature bug: https://bugzilla.redhat.com/show_bug.cgi?id=2005153 + + +# As of Feb 2024 this 1300+ bugs, which would have hit the query limit of 1000 +query = bzapi.build_query( + product="Fedora", + component="virt-manager") +# Request the bugzilla.redhat.com extension ids_only=True to bypass limit +query["ids_only"] = True + +queried_bugs = bzapi.query(query) +ids = [bug.id for bug in queried_bugs] +print(f"Queried {len(ids)} ids") + + +# Use getbugs to fetch the full list. getbugs is not affected by +# default RHBZ limits. However, requesting too much data via getbugs +# will timeout. This paginates the lookup to query 1000 bugs at a time. +# +# We also limit the returned data to just give us the `summary`. +# You should always limit your queries with include_fields` to only return +# the data you need. +count = 0 +pagesize = 1000 +include_fields = ["summary"] +while count < len(ids): + idslice = ids[count:(count + pagesize)] + print(f"Fetching data for bugs {count}-{count+len(idslice)-1}") + bugs = bzapi.getbugs(idslice, include_fields=include_fields) + print(f"Fetched {len(bugs)} bugs") + count += pagesize diff --git a/examples/update.py b/examples/update.py index 1da11470..86b1967c 100644 --- a/examples/update.py +++ b/examples/update.py @@ -5,14 +5,12 @@ # update.py: Make changes to an existing bug -from __future__ import print_function - import time import bugzilla # public test instance of bugzilla.redhat.com. It's okay to make changes -URL = "partner-bugzilla.redhat.com" +URL = "bugzilla.stage.redhat.com" bzapi = bugzilla.Bugzilla(URL) if not bzapi.logged_in: print("This example requires cached login credentials for %s" % URL) @@ -25,9 +23,9 @@ # The param names map to those accepted by Bugzilla Bug.update: # https://bugzilla.readthedocs.io/en/latest/api/core/v1/bug.html#update-bug # -# Example bug: https://partner-bugzilla.redhat.com/show_bug.cgi?id=427301 +# Example bug: https://bugzilla.stage.redhat.com/show_bug.cgi?id=427301 # Don't worry, changing things here is fine, and won't send any email to -# users or anything. It's what partner-bugzilla.redhat.com is for! +# users or anything. It's what bugzilla.stage.redhat.com is for! bug = bzapi.getbug(427301) print("Bug id=%s original summary=%s" % (bug.id, bug.summary)) @@ -40,7 +38,7 @@ # Now let's add a comment -comments = bug.getcomments() +comments = bug.get_comments() print("Bug originally has %d comments" % len(comments)) update = bzapi.build_update(comment="new example comment %s" % time.time()) @@ -48,7 +46,7 @@ # refresh() actually isn't required here because comments are fetched # on demand -comments = bug.getcomments() +comments = bug.get_comments() print("Bug now has %d comments. Last comment=%s" % (len(comments), comments[-1]["text"])) diff --git a/man/bugzilla.1 b/man/bugzilla.1 new file mode 100644 index 00000000..fba29568 --- /dev/null +++ b/man/bugzilla.1 @@ -0,0 +1,657 @@ +.\" Man page generated from reStructuredText. +. +. +.nr rst2man-indent-level 0 +. +.de1 rstReportMargin +\\$1 \\n[an-margin] +level \\n[rst2man-indent-level] +level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] +- +\\n[rst2man-indent0] +\\n[rst2man-indent1] +\\n[rst2man-indent2] +.. +.de1 INDENT +.\" .rstReportMargin pre: +. RS \\$1 +. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] +. nr rst2man-indent-level +1 +.\" .rstReportMargin post: +.. +.de UNINDENT +. RE +.\" indent \\n[an-margin] +.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] +.nr rst2man-indent-level -1 +.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] +.in \\n[rst2man-indent\\n[rst2man-indent-level]]u +.. +.TH "BUGZILLA" 1 "" "" "User Commands" +.SH NAME +bugzilla \- command line tool for interacting with Bugzilla +.SH SYNOPSIS +.sp +\fBbugzilla\fP [\fIoptions\fP] [\fIcommand\fP] [\fIcommand\-options\fP] +.SH DESCRIPTION +.sp +\fBbugzilla\fP is a command line tool for interacting with a Bugzilla +instance over REST or XMLRPC. +.nf + +\fIcommand\fP is one of: +* login \- log into the given bugzilla instance +* new \- create a new bug +* query \- search for bugs matching given criteria +* modify \- modify existing bugs +* attach \- attach files to existing bugs, or get attachments +* info \- get info about the given bugzilla instance +.fi +.sp +.SH GLOBAL OPTIONS +.SS \fB\-\-help, \-h\fP +.sp +\fBSyntax:\fP \fB\-h\fP +.sp +show this help message and exit +.SS \fB\-\-bugzilla\fP +.sp +\fBSyntax:\fP \fB\-\-bugzilla\fP BUGZILLA +.sp +The bugzilla URL. Full API URLs are typically like: +.nf + +* \fI\%https://bugzilla.example.com/xmlrpc.cgi\fP # XMLRPC API +* \fI\%https://bugzilla.example.com/rest/\fP # REST API + +.fi +.sp +.sp +If a non\-specific URL is passed, like \(aqbugzilla.redhat.com\(aq, \fBbugzilla\fP +will try to probe whether the expected XMLRPC or REST path is available, +preferring XMLRPC for backwards compatibility. +.sp +The default URL \fI\%https://bugzilla.redhat.com\fP +.SS \fB\-\-nosslverify\fP +.sp +\fBSyntax:\fP \fB\-\-nosslverify\fP +.sp +Don\(aqt error on invalid bugzilla SSL certificate +.SS \fB\-\-cert\fP +.sp +\fBSyntax:\fP \fB\-\-cert\fP CERTFILE +.sp +client side certificate file needed by the webserver. +.SS \fB\-\-login\fP +.sp +\fBSyntax:\fP \fB\-\-login\fP +.sp +Run interactive \(dqlogin\(dq before performing the specified command. +.SS \fB\-\-username\fP +.sp +\fBSyntax:\fP \fB\-\-username\fP USERNAME +.sp +Log in with this username +.SS \fB\-\-password\fP +.sp +\fBSyntax:\fP \fB\-\-password\fP PASSWORD +.sp +Log in with this password +.SS \fB\-\-restrict\-login\fP +.sp +\fBSyntax:\fP \fB\-\-restrict\-login\fP +.sp +The session (login token) will be restricted to the current IP +address. +.SS \fB\-\-ensure\-logged\-in\fP +.sp +\fBSyntax:\fP \fB\-\-ensure\-logged\-in\fP +.sp +Raise an error if we aren\(aqt logged in to bugzilla. Consider using +this if you are depending on cached credentials, to ensure that when +they expire the tool errors, rather than subtly change output. +.SS \fB\-\-no\-cache\-credentials\fP +.sp +\fBSyntax:\fP \fB\-\-no\-cache\-credentials\fP +.sp +Don\(aqt save any bugzilla tokens to disk, and don\(aqt use any +pre\-existing credentials. +.SS \fB\-\-tokenfile\fP +.sp +\fBSyntax:\fP \fB\-\-tokenfile\fP TOKENFILE +.sp +token file to use for bugzilla authentication +.SS \fB\-\-verbose\fP +.sp +\fBSyntax:\fP \fB\-\-verbose\fP +.sp +give more info about what\(aqs going on +.SS \fB\-\-debug\fP +.sp +\fBSyntax:\fP \fB\-\-debug\fP +.sp +output bunches of debugging info +.SS \fB\-\-version\fP +.sp +\fBSyntax:\fP \fB\-\-version\fP +.sp +show program\(aqs version number and exit +.SH STANDARD BUGZILLA OPTIONS +.sp +These options are shared by some combination of the \(aqnew\(aq, \(aqquery\(aq, and +\(aqmodify\(aq sub commands. Not every option works for each command though. +.SS \fB\-p, \-\-product\fP +.sp +\fBSyntax:\fP \fB\-\-product\fP PRODUCT +.sp +Product name +.SS \fB\-v, \-\-version\fP +.sp +\fBSyntax:\fP \fB\-\-version\fP VERSION +.sp +Product version +.SS \fB\-c, \-\-component\fP +.sp +\fBSyntax:\fP \fB\-\-component\fP COMPONENT +.sp +Component name +.SS \fB\-s, \-\-summary\fP +.sp +\fBSyntax:\fP \fB\-\-summary\fP SUMMARY +.sp +Bug summary +.SS \fB\-l, \-\-comment\fP +.sp +\fBSyntax:\fP \fB\-\-comment\fP DESCRIPTION +.sp +Set initial bug comment/description +.SS \fB\-\-comment\-tag\fP +.sp +\fBSyntax:\fP \fB\-\-comment\-tag\fP TAG +.sp +Comment tag for the new comment +.SS \fB\-\-sub\-component\fP +.sp +\fBSyntax:\fP \fB\-\-sub\-component\fP SUB_COMPONENT +.sp +RHBZ sub component name +.SS \fB\-o, \-\-os\fP +.sp +\fBSyntax:\fP \fB\-\-os\fP OS +.sp +Operating system +.SS \fB\-\-arch\fP +.sp +\fBSyntax:\fP \fB\-\-arch\fP ARCH +.sp +Arch this bug occurs on +.SS \fB\-x, \-\-severity\fP +.sp +\fBSyntax:\fP \fB\-\-severity\fP SEVERITY +.sp +Bug severity +.SS \fB\-z, \-\-priority\fP +.sp +\fBSyntax:\fP \fB\-\-priority\fP PRIORITY +.sp +Bug priority +.SS \fB\-\-alias\fP +.sp +\fBSyntax:\fP \fB\-\-alias\fP ALIAS +.sp +Bug alias (name) +.SS \fB\-s, \-\-status\fP +.sp +\fBSyntax:\fP \fB\-\-status\fP STATUS +.sp +Bug status (NEW, ASSIGNED, etc.) +.SS \fB\-u, \-\-url\fP +.sp +\fBSyntax:\fP \fB\-\-url\fP URL +.sp +URL for further bug info +.SS \fB\-m \-\-target_milestone\fP +.sp +\fBSyntax:\fP \fB\-\-target_milestone\fP TARGET_MILESTONE +.sp +Target milestone +.SS \fB\-\-target_release\fP +.sp +\fBSyntax:\fP \fB\-\-target_release\fP TARGET_RELEASE +.sp +RHBZ Target release +.SS \fB\-\-blocked\fP +.sp +\fBSyntax:\fP \fB\&...]\fP +.sp +Bug IDs that this bug blocks +.SS \fB\-\-dependson\fP +.sp +\fBSyntax:\fP \fB\&...]\fP +.sp +Bug IDs that this bug depends on +.SS \fB\-\-keywords\fP +.sp +\fBSyntax:\fP \fB\&...]\fP +.sp +Bug keywords +.SS \fB\-\-groups\fP +.sp +\fBSyntax:\fP \fB\&...]\fP +.sp +Which user groups can view this bug +.SS \fB\-\-cc\fP +.sp +\fBSyntax:\fP \fB\&...]\fP +.sp +CC list +.SS \fB\-a, \-\-assignee, \-\-assigned_to\fP +.sp +\fBSyntax:\fP \fB\-\-assigned_to\fP ASSIGNED_TO +.sp +Bug assignee +.SS \fB\-q, \-\-qa_contact\fP +.sp +\fBSyntax:\fP \fB\-\-qa_contact\fP QA_CONTACT +.sp +QA contact +.SS \fB\-f, \-\-flag\fP +.sp +\fBSyntax:\fP \fB\-\-flag\fP FLAG +.sp +Set or unset a flag. For example, to set a flag named devel_ack, do +\-\-flag devel_ack+ Unset a flag with the \(aqX\(aq value, like \-\-flag +needinfoX +.SS \fB\-\-tags\fP +.sp +\fBSyntax:\fP \fB\-\-tags\fP TAG +.sp +Set (personal) tags field +.SS \fB\-w, \-\-whiteboard\fP +.sp +\fBSyntax:\fP \fB\-\-whiteboard\fP WHITEBOARD +.sp +Whiteboard field +.SS \fB\-\-devel_whiteboard\fP +.sp +\fBSyntax:\fP \fB\-\-devel_whiteboard\fP DEVEL_WHITEBOARD +.sp +RHBZ devel whiteboard field +.SS \fB\-\-internal_whiteboard\fP +.sp +\fBSyntax:\fP \fB\-\-internal_whiteboard\fP INTERNAL_WHITEBOARD +.sp +RHBZ internal whiteboard field +.SS \fB\-\-qa_whiteboard\fP +.sp +\fBSyntax:\fP \fB\-\-qa_whiteboard\fP QA_WHITEBOARD +.sp +RHBZ QA whiteboard field +.SS \fB\-F, \-\-fixed_in\fP +.sp +\fBSyntax:\fP \fB\-\-fixed_in\fP FIXED_IN +.sp +RHBZ \(aqFixed in version\(aq field +.SS \fB\-\-field\fP +.sp +\fBSyntax:\fP \fB\-\-field\fP FIELD=VALUE +.sp +Manually specify a bugzilla API field. FIELD is the raw name used +by the bugzilla instance. For example if your bugzilla instance has a +custom field cf_my_field, do: \-\-field cf_my_field=VALUE +.SS \fB\-\-field\-json\fP +.sp +\fBSyntax:\fP \fB\-\-field\-json\fP JSONSTRING +.sp +Specify \-\-field data as a JSON string. Example: +\-\-field\-json \(aq{\(dqcf_my_field\(dq: \(dqVALUE\(dq, \(dqcf_array_field\(dq: [1, 2]}\(aq +.SH OUTPUT OPTIONS +.sp +These options are shared by several commands, for tweaking the text +output of the command results. +.SS \fB\-\-full\fP +.sp +\fBSyntax:\fP \fB\-\-full\fP +.sp +output detailed bug info +.SS \fB\-i, \-\-ids\fP +.sp +\fBSyntax:\fP \fB\-\-ids\fP +.sp +output only bug IDs +.SS \fB\-e, \-\-extra\fP +.sp +\fBSyntax:\fP \fB\-\-extra\fP +.sp +output additional bug information (keywords, Whiteboards, etc.) +.SS \fB\-\-oneline\fP +.sp +\fBSyntax:\fP \fB\-\-oneline\fP +.sp +one line summary of the bug (useful for scripts) +.SS \fB\-\-json\fP +.sp +\fBSyntax:\fP \fB\-\-json\fP +.sp +output bug contents in JSON format +.SS \fB\-\-includefield\fP +.sp +\fBSyntax:\fP \fB\-\-includefield\fP +.sp +Pass the field name to bugzilla include_fields list. +Only the fields passed to include_fields are returned +by the bugzilla server. +This can be specified multiple times. +.SS \fB\-\-extrafield\fP +.sp +\fBSyntax:\fP \fB\-\-extrafield\fP +.sp +Pass the field name to bugzilla extra_fields list. +When used with \-\-json this can be used to request +bugzilla to return values for non\-default fields. +This can be specified multiple times. +.SS \fB\-\-excludefield\fP +.sp +\fBSyntax:\fP \fB\-\-excludefield\fP +.sp +Pass the field name to bugzilla exclude_fields list. +When used with \-\-json this can be used to request +bugzilla to not return values for a field. +This can be specified multiple times. +.SS \fB\-\-raw\fP +.sp +\fBSyntax:\fP \fB\-\-raw\fP +.sp +raw output of the bugzilla contents. This format is unstable and +difficult to parse. Please use the \fB\-\-json\fP instead if you want +maximum output from the \fIbugzilla\fP +.SS \fB\-\-outputformat\fP +.sp +\fBSyntax:\fP \fB\-\-outputformat\fP OUTPUTFORMAT +.sp +Print output in the form given. You can use RPM\-style tags that match +bug fields, e.g.: \(aq%{id}: %{summary}\(aq. +.sp +The output of the bugzilla tool should NEVER BE PARSED unless you are +using a custom \-\-outputformat. For everything else, just don\(aqt parse it, +the formats are not stable and are subject to change. +.sp +\-\-outputformat allows printing arbitrary bug data in a user preferred +format. For example, to print a returned bug ID, component, and product, +separated with ::, do: +.sp +\-\-outputformat \(dq%{id}::%{component}::%{product}\(dq +.sp +The fields (like \(aqid\(aq, \(aqcomponent\(aq, etc.) are the names of the values +returned by bugzilla\(aqs API. To see a list of all fields, +check the API documentation in the \(aqSEE ALSO\(aq section. Alternatively, +run a \(aqbugzilla \-\-debug query ...\(aq and look at the key names returned in +the query results. Also, in most cases, using the name of the associated +command line switch should work, like \-\-bug_status becomes +%{bug_status}, etc. +.SH ‘QUERY’ SPECIFIC OPTIONS +.sp +Certain options can accept a comma separated list to query multiple +values, including \-\-status, \-\-component, \-\-product, \-\-version, \-\-id. +.sp +Note: querying via explicit command line options will only get you so +far. See the \-\-from\-url option for a way to use powerful Web UI queries +from the command line. +.SS \fB\-b, \-\-bug_id, \-\-id\fP +.sp +\fBSyntax:\fP \fB\-\-id\fP ID +.sp +specify individual bugs by IDs, separated with commas +.SS \fB\-r, \-\-reporter\fP +.sp +\fBSyntax:\fP \fB\-\-reporter\fP REPORTER +.sp +Email: search reporter email for given address +.SS \fB\-\-quicksearch\fP +.sp +\fBSyntax:\fP \fB\-\-quicksearch\fP QUICKSEARCH +.sp +Search using bugzilla\(aqs quicksearch functionality. +.SS \fB\-\-savedsearch\fP +.sp +\fBSyntax:\fP \fB\-\-savedsearch\fP SAVEDSEARCH +.sp +Name of a bugzilla saved search. If you don\(aqt own this saved search, +you must passed \-\-savedsearch_sharer_id. +.SS \fB\-\-savedsearch\-sharer\-id\fP +.sp +\fBSyntax:\fP \fB\-\-savedsearch\-sharer\-id\fP SAVEDSEARCH_SHARER_ID +.sp +Owner ID of the \-\-savedsearch. You can get this ID from the URL +bugzilla generates when running the saved search from the web UI. +.SS \fB\-\-from\-url\fP +.sp +\fBSyntax:\fP \fB\-\-from\-url\fP WEB_QUERY_URL +.sp +Make a working query via bugzilla\(aqs \(aqAdvanced search\(aq web UI, grab +the url from your browser (the string with query.cgi or buglist.cgi +in it), and \-\-from\-url will run it via the bugzilla API. Don\(aqt forget +to quote the string! This only works for Bugzilla 5 and Red Hat +bugzilla +.SH ‘MODIFY’ SPECIFIC OPTIONS +.sp +Fields that take multiple values have a special input format. +.nf +Append: \fI\%\-\-cc=foo@example.com\fP +Overwrite: \fI\%\-\-cc==foo@example.com\fP +Remove: \fI\%\-\-cc=\-foo@example.com\fP +.fi +.sp +.sp +Options that accept this format: \-\-cc, \-\-blocked, \-\-dependson, \-\-groups, +\-\-tags, whiteboard fields. +.SS \fB\-k, \-\-close RESOLUTION\fP +.sp +\fBSyntax:\fP \fBRESOLUTION\fP +.sp +Close with the given resolution (WONTFIX, NOTABUG, etc.) +.SS \fB\-d, \-\-dupeid\fP +.sp +\fBSyntax:\fP \fB\-\-dupeid\fP ORIGINAL +.sp +ID of original bug. Implies \-\-close DUPLICATE +.SS \fB\-\-private\fP +.sp +\fBSyntax:\fP \fB\-\-private\fP +.sp +Mark new comment as private +.SS \fB\-\-reset\-assignee\fP +.sp +\fBSyntax:\fP \fB\-\-reset\-assignee\fP +.sp +Reset assignee to component default +.SS \fB\-\-reset\-qa\-contact\fP +.sp +\fBSyntax:\fP \fB\-\-reset\-qa\-contact\fP +.sp +Reset QA contact to component default +.SS \fB\-\-minor\-update\fP +.sp +\fBSyntax:\fP \fB\-\-minor\-update\fP +.sp +Request bugzilla to not send any email about this change +.SH ‘NEW’ SPECIFIC OPTIONS +.SS \fB\-\-private\fP +.sp +\fBSyntax:\fP \fB\-\-private\fP +.sp +Mark new comment as private +.SH ‘ATTACH’ OPTIONS +.SS \fB\-f, \-\-file\fP +.sp +\fBSyntax:\fP \fB\-\-file\fP FILENAME +.sp +File to attach, or filename for data provided on stdin +.SS \fB\-d, \-\-description\fP +.sp +\fBSyntax:\fP \fB\-\-description\fP DESCRIPTION +.sp +A short description of the file being attached +.SS \fB\-t, \-\-type\fP +.sp +\fBSyntax:\fP \fB\-\-type\fP MIMETYPE +.sp +Mime\-type for the file being attached +.SS \fB\-g, \-\-get\fP +.sp +\fBSyntax:\fP \fB\-\-get\fP ATTACHID +.sp +Download the attachment with the given ID +.SS \fB\-\-getall\fP +.sp +\fBSyntax:\fP \fB\-\-getall\fP BUGID +.sp +Download all attachments on the given bug +.SS \fB\-\-ignore\-obsolete\fP +.sp +\fBSyntax:\fP \fB\-\-ignore\-obsolete\fP +.sp +Do not download attachments marked as obsolete. +.SS \fB\-l, \-\-comment\fP +.sp +\fBSyntax:\fP \fB\-\-comment\fP COMMENT +.sp +Add comment with attachment +.SH ‘INFO’ OPTIONS +.SS \fB\-p, \-\-products\fP +.sp +\fBSyntax:\fP \fB\-\-products\fP +.sp +Get a list of products +.SS \fB\-c, \-\-components\fP +.sp +\fBSyntax:\fP \fB\-\-components\fP PRODUCT +.sp +List the components in the given product +.SS \fB\-o, \-\-component_owners\fP +.sp +\fBSyntax:\fP \fB\-\-component_owners\fP PRODUCT +.sp +List components (and their owners) +.SS \fB\-v, \-\-versions\fP +.sp +\fBSyntax:\fP \fB\-\-versions\fP PRODUCT +.sp +List the versions for the given product +.SS \fB\-\-active\-components\fP +.sp +\fBSyntax:\fP \fB\-\-active\-components\fP +.sp +Only show active components. Combine with \-\-components* +.SH BUGZILLARC CONFIG FILE +.sp +Both \fBbugzilla\fP and the python\-bugzilla library will read +a \fBbugzillarc\fP config file if it is present in the following +locations: +.INDENT 0.0 +.IP \(bu 2 +/etc/bugzillarc +.IP \(bu 2 +~/.bugzillarc +.IP \(bu 2 +~/.config/python\-bugzilla/bugzillarc +.UNINDENT +.sp +The contents of the files are processed and merged together +in the order they are listed above. +.sp +The main usage for \fBbugzillarc\fP is to store API keys for your +bugzilla URLs: +.INDENT 0.0 +.INDENT 3.5 +.sp +.nf +.ft C +[bugzilla.example.com] +api_key=INSERT\-YOUR\-API\-KEY\-HERE + +[bugzilla.redhat.com] +api_key=MY\-REDHAT\-API\-KEY\-BLAH +.ft P +.fi +.UNINDENT +.UNINDENT +.sp +The sections must be hostnames. Other values that can be +set per hostname section are +.INDENT 0.0 +.IP \(bu 2 +\fBuser\fP: default auth username +.IP \(bu 2 +\fBpassword\fP: default auth password +.IP \(bu 2 +\fBcert\fP: default client side certificate +.UNINDENT +.sp +A \fB[DEFAULTS]\fP section is also accepted, which takes the following +values: +.INDENT 0.0 +.IP \(bu 2 +\fBurl\fP: default bugzilla URL +.UNINDENT +.SH AUTHENTICATION CACHE AND API KEYS +.sp +Some command usage will require an active login to the bugzilla +instance. For example, if the bugzilla instance has some private bugs, +those bugs will be missing from \(aqquery\(aq output if you do not have an +active login. +.sp +If you are connecting to a bugzilla 5.0 or later instance, the best +option is to use bugzilla API keys. From the bugzilla web UI, log in, +navigate to Preferences\->API Keys, and generate a key (it will be a long +string of characters and numbers). +.sp +Then use \(aqbugzilla \-\-bugzilla URL login \-\-api\-key\(aq, which will ask +for the API key, and save it to \fBbugzillarc\fP for you. +.sp +For older bugzilla instances, you will need to cache a login token +with the \(dqlogin\(dq subcommand or the \(dq\-\-login\(dq argument. +.sp +Additionally, the \-\-no\-cache\-credentials option will tell the bugzilla +tool to \fInot\fP save or use any authentication cache, including the +\fBbugzillarc\fP file. +.SH EXAMPLES +.nf +bugzilla query \-\-bug_id 62037 + +bugzilla query \-\-version 15 \-\-component python\-bugzilla + +bugzilla login + +bugzilla new \-p Fedora \-v rawhide \-c python\-bugzilla \e +.in +2 +\-\-summary \(dqpython\-bugzilla causes headaches\(dq \e +\-\-comment \(dqpython\-bugzilla made my brain hurt when I used it.\(dq + +.in -2 +bugzilla attach \-\-file ~/Pictures/cam1.jpg \-\-desc \(dqme, in pain\(dq +$BUGID + +bugzilla attach \-\-getall $BUGID + +bugzilla modify \-\-close NOTABUG \-\-comment \(dqActually, you\(aqre +hungover.\(dq $BUGID +.fi +.sp +.SH EXIT STATUS +.sp +\fBbugzilla\fP normally returns 0 if the requested command was successful. +Otherwise, exit status is 1 if \fBbugzilla\fP is interrupted by the user +(or a login attempt fails), 2 if a socket error occurs (e.g. TCP +connection timeout), and 3 if the Bugzilla server throws an error. +.SH BUGS +.sp +Please report any bugs as github issues at +\fI\%https://github.com/python\-bugzilla/python\-bugzilla\fP +.SH SEE ALSO +.sp +\fI\%https://bugzilla.readthedocs.io/en/latest/api/index.html\fP +.sp +\fI\%https://bugzilla.redhat.com/docs/en/html/api/core/v1/bug.html\fP +.\" Generated by docutils manpage writer. +. diff --git a/man/bugzilla.rst b/man/bugzilla.rst index c456d6da..09ff4132 100644 --- a/man/bugzilla.rst +++ b/man/bugzilla.rst @@ -32,14 +32,22 @@ instance over REST or XMLRPC. | * info - get info about the given bugzilla instance + GLOBAL OPTIONS --------------- +============== + +``--help, -h`` +^^^^^^^^^^^^^^ -- ``--help, -h`` +**Syntax:** ``-h`` show this help message and exit -- ``--bugzilla=BUGZILLA`` + +``--bugzilla`` +^^^^^^^^^^^^^^ + +**Syntax:** ``--bugzilla`` BUGZILLA The bugzilla URL. Full API URLs are typically like: @@ -54,252 +62,471 @@ preferring XMLRPC for backwards compatibility. The default URL https://bugzilla.redhat.com -- ``--nosslverify`` + +``--nosslverify`` +^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--nosslverify`` Don't error on invalid bugzilla SSL certificate -- ``--cert=CERTFILE`` + +``--cert`` +^^^^^^^^^^ + +**Syntax:** ``--cert`` CERTFILE client side certificate file needed by the webserver. -- ``--login`` + +``--login`` +^^^^^^^^^^^ + +**Syntax:** ``--login`` Run interactive "login" before performing the specified command. -- ``--username=USERNAME`` + +``--username`` +^^^^^^^^^^^^^^ + +**Syntax:** ``--username`` USERNAME Log in with this username -- ``--password=PASSWORD`` + +``--password`` +^^^^^^^^^^^^^^ + +**Syntax:** ``--password`` PASSWORD Log in with this password -- ``--restrict-login`` + +``--restrict-login`` +^^^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--restrict-login`` The session (login token) will be restricted to the current IP address. -- ``--ensure-logged-in`` + +``--ensure-logged-in`` +^^^^^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--ensure-logged-in`` Raise an error if we aren't logged in to bugzilla. Consider using this if you are depending on cached credentials, to ensure that when they expire the tool errors, rather than subtly change output. -- ``--no-cache-credentials`` -Don't save any bugzilla cookies or tokens to disk, and don't use any +``--no-cache-credentials`` +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--no-cache-credentials`` + +Don't save any bugzilla tokens to disk, and don't use any pre-existing credentials. -- ``--cookiefile=COOKIEFILE`` -cookie file to use for bugzilla authentication +``--tokenfile`` +^^^^^^^^^^^^^^^ -- ``--tokenfile=TOKENFILE`` +**Syntax:** ``--tokenfile`` TOKENFILE token file to use for bugzilla authentication -- ``--verbose`` + +``--verbose`` +^^^^^^^^^^^^^ + +**Syntax:** ``--verbose`` give more info about what's going on -- ``--debug`` + +``--debug`` +^^^^^^^^^^^ + +**Syntax:** ``--debug`` output bunches of debugging info -- ``--version`` + +``--version`` +^^^^^^^^^^^^^ + +**Syntax:** ``--version`` show program's version number and exit + Standard bugzilla options ========================= These options are shared by some combination of the 'new', 'query', and 'modify' sub commands. Not every option works for each command though. -- ``--product=PRODUCT, -p PRODUCT`` + +``-p, --product`` +^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--product`` PRODUCT Product name -- ``--version=VERSION, -v VERSION`` + +``-v, --version`` +^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--version`` VERSION Product version -- ``--component=COMPONENT, -c COMPONENT`` + +``-c, --component`` +^^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--component`` COMPONENT Component name -- ``--summary=SUMMARY, -s SUMMARY, --short_desc=SUMMARY`` + +``-s, --summary`` +^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--summary`` SUMMARY Bug summary -- ``--comment=DESCRIPTION, -l DESCRIPTION`` + +``-l, --comment`` +^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--comment`` DESCRIPTION Set initial bug comment/description -- ``--comment-tag=TAG`` + +``--comment-tag`` +^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--comment-tag`` TAG Comment tag for the new comment -- ``--sub-component=SUB_COMPONENT`` + +``--sub-component`` +^^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--sub-component`` SUB_COMPONENT RHBZ sub component name -- ``--os=OS, -o OS`` + +``-o, --os`` +^^^^^^^^^^^^ + +**Syntax:** ``--os`` OS Operating system -- ``--arch=ARCH`` + +``--arch`` +^^^^^^^^^^ + +**Syntax:** ``--arch`` ARCH Arch this bug occurs on -- ``--severity=SEVERITY, -x SEVERITY`` + +``-x, --severity`` +^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--severity`` SEVERITY Bug severity -- ``--priority=PRIORITY, -z PRIORITY`` + +``-z, --priority`` +^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--priority`` PRIORITY Bug priority -- ``--alias=ALIAS`` + +``--alias`` +^^^^^^^^^^^ + +**Syntax:** ``--alias`` ALIAS Bug alias (name) -- ``--status=STATUS, -s STATUS, --bug_status=STATUS`` + +``-s, --status`` +^^^^^^^^^^^^^^^^ + +**Syntax:** ``--status`` STATUS Bug status (NEW, ASSIGNED, etc.) -- ``--url=URL, -u URL`` + +``-u, --url`` +^^^^^^^^^^^^^ + +**Syntax:** ``--url`` URL URL for further bug info -- ``--target_milestone=TARGET_MILESTONE, -m TARGET_MILESTONE`` + +``-m --target_milestone`` +^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--target_milestone`` TARGET_MILESTONE Target milestone -- ``--target_release=TARGET_RELEASE`` + +``--target_release`` +^^^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--target_release`` TARGET_RELEASE RHBZ Target release -- ``--blocked=BUGID[, BUGID, ...]`` + +``--blocked`` +^^^^^^^^^^^^^ + +**Syntax:** ``...]`` Bug IDs that this bug blocks -- ``--dependson=BUGID[, BUGID, ...]`` + +``--dependson`` +^^^^^^^^^^^^^^^ + +**Syntax:** ``...]`` Bug IDs that this bug depends on -- ``--keywords=KEYWORD[, KEYWORD, ...]`` + +``--keywords`` +^^^^^^^^^^^^^^ + +**Syntax:** ``...]`` Bug keywords -- ``--groups=GROUP[, GROUP, ...]`` + +``--groups`` +^^^^^^^^^^^^ + +**Syntax:** ``...]`` Which user groups can view this bug -- ``--cc=CC[, CC, ...]`` + +``--cc`` +^^^^^^^^ + +**Syntax:** ``...]`` CC list -- ``--assigned_to=ASSIGNED_TO, -a ASSIGNED_TO, --assignee ASSIGNED_TO`` + +``-a, --assignee, --assigned_to`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--assigned_to`` ASSIGNED_TO Bug assignee -- ``--qa_contact=QA_CONTACT, -q QA_CONTACT`` + +``-q, --qa_contact`` +^^^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--qa_contact`` QA_CONTACT QA contact -- ``--flag=FLAG`` + +``-f, --flag`` +^^^^^^^^^^^^^^ + +**Syntax:** ``--flag`` FLAG Set or unset a flag. For example, to set a flag named devel_ack, do --flag devel_ack+ Unset a flag with the 'X' value, like --flag needinfoX -- ``--tags=TAG`` + +``--tags`` +^^^^^^^^^^ + +**Syntax:** ``--tags`` TAG Set (personal) tags field -- ``--whiteboard WHITEBOARD, -w WHITEBOARD, --status_whiteboard WHITEBOARD`` + +``-w, --whiteboard`` +^^^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--whiteboard`` WHITEBOARD Whiteboard field -- ``--devel_whiteboard DEVEL_WHITEBOARD`` + +``--devel_whiteboard`` +^^^^^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--devel_whiteboard`` DEVEL_WHITEBOARD RHBZ devel whiteboard field -- ``--internal_whiteboard INTERNAL_WHITEBOARD`` + +``--internal_whiteboard`` +^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--internal_whiteboard`` INTERNAL_WHITEBOARD RHBZ internal whiteboard field -- ``--qa_whiteboard QA_WHITEBOARD`` + +``--qa_whiteboard`` +^^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--qa_whiteboard`` QA_WHITEBOARD RHBZ QA whiteboard field -- ``--fixed_in FIXED_IN, -F FIXED_IN`` + +``-F, --fixed_in`` +^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--fixed_in`` FIXED_IN RHBZ 'Fixed in version' field -- ``--field=FIELD=VALUE`` + +``--field`` +^^^^^^^^^^^ + +**Syntax:** ``--field`` FIELD=VALUE Manually specify a bugzilla API field. FIELD is the raw name used by the bugzilla instance. For example if your bugzilla instance has a custom field cf_my_field, do: --field cf_my_field=VALUE +``--field-json`` +^^^^^^^^^^^^^^^^ + +**Syntax:** ``--field-json`` JSONSTRING + +Specify --field data as a JSON string. Example: +--field-json '{"cf_my_field": "VALUE", "cf_array_field": [1, 2]}' + + + Output options ============== These options are shared by several commands, for tweaking the text output of the command results. -- ``--full, -f`` + +``--full`` +^^^^^^^^^^ + +**Syntax:** ``--full`` output detailed bug info -- ``--ids, -i`` + +``-i, --ids`` +^^^^^^^^^^^^^ + +**Syntax:** ``--ids`` output only bug IDs -- ``--extra, -e`` + +``-e, --extra`` +^^^^^^^^^^^^^^^ + +**Syntax:** ``--extra`` output additional bug information (keywords, Whiteboards, etc.) -- ``--oneline`` + +``--oneline`` +^^^^^^^^^^^^^ + +**Syntax:** ``--oneline`` one line summary of the bug (useful for scripts) -- ``--json`` + +``--json`` +^^^^^^^^^^ + +**Syntax:** ``--json`` output bug contents in JSON format -- ``--includefield`` + +``--includefield`` +^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--includefield`` Pass the field name to bugzilla include_fields list. Only the fields passed to include_fields are returned by the bugzilla server. This can be specified multiple times. -- ``--extrafield`` + +``--extrafield`` +^^^^^^^^^^^^^^^^ + +**Syntax:** ``--extrafield`` Pass the field name to bugzilla extra_fields list. When used with --json this can be used to request bugzilla to return values for non-default fields. This can be specified multiple times. -- ``--excludefield`` + +``--excludefield`` +^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--excludefield`` Pass the field name to bugzilla exclude_fields list. When used with --json this can be used to request bugzilla to not return values for a field. This can be specified multiple times. -- ``--raw`` + +``--raw`` +^^^^^^^^^ + +**Syntax:** ``--raw`` raw output of the bugzilla contents. This format is unstable and difficult to parse. Please use the ``--json`` instead if you want maximum output from the `bugzilla` -- ``--outputformat=OUTPUTFORMAT`` + +``--outputformat`` +^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--outputformat`` OUTPUTFORMAT Print output in the form given. You can use RPM-style tags that match bug fields, e.g.: '%{id}: %{summary}'. @@ -333,29 +560,53 @@ Note: querying via explicit command line options will only get you so far. See the --from-url option for a way to use powerful Web UI queries from the command line. -- ``--id ID, -b ID, --bug_id ID`` + +``-b, --bug_id, --id`` +^^^^^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--id`` ID specify individual bugs by IDs, separated with commas -- ``--reporter REPORTER, -r REPORTER`` + +``-r, --reporter`` +^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--reporter`` REPORTER Email: search reporter email for given address -- ``--quicksearch QUICKSEARCH`` + +``--quicksearch`` +^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--quicksearch`` QUICKSEARCH Search using bugzilla's quicksearch functionality. -- ``--savedsearch SAVEDSEARCH`` + +``--savedsearch`` +^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--savedsearch`` SAVEDSEARCH Name of a bugzilla saved search. If you don't own this saved search, you must passed --savedsearch_sharer_id. -- ``--savedsearch-sharer-id SAVEDSEARCH_SHARER_ID`` + +``--savedsearch-sharer-id`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--savedsearch-sharer-id`` SAVEDSEARCH_SHARER_ID Owner ID of the --savedsearch. You can get this ID from the URL bugzilla generates when running the saved search from the web UI. -- ``--from-url WEB_QUERY_URL`` + +``--from-url`` +^^^^^^^^^^^^^^ + +**Syntax:** ``--from-url`` WEB_QUERY_URL Make a working query via bugzilla's 'Advanced search' web UI, grab the url from your browser (the string with query.cgi or buglist.cgi @@ -376,63 +627,123 @@ Fields that take multiple values have a special input format. Options that accept this format: --cc, --blocked, --dependson, --groups, --tags, whiteboard fields. -- ``--close RESOLUTION, -k RESOLUTION`` + +``-k, --close RESOLUTION`` +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``RESOLUTION`` Close with the given resolution (WONTFIX, NOTABUG, etc.) -- ``--dupeid ORIGINAL, -d ORIGINAL`` + +``-d, --dupeid`` +^^^^^^^^^^^^^^^^ + +**Syntax:** ``--dupeid`` ORIGINAL ID of original bug. Implies --close DUPLICATE -- ``--private`` + +``--private`` +^^^^^^^^^^^^^ + +**Syntax:** ``--private`` Mark new comment as private -- ``--reset-assignee`` + +``--reset-assignee`` +^^^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--reset-assignee`` Reset assignee to component default -- ``--reset-qa-contact`` + +``--reset-qa-contact`` +^^^^^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--reset-qa-contact`` Reset QA contact to component default +``--minor-update`` +^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--minor-update`` + +Request bugzilla to not send any email about this change + + + ‘new’ specific options ====================== -- ``--private`` +``--private`` +^^^^^^^^^^^^^ + +**Syntax:** ``--private`` Mark new comment as private + ‘attach’ options ================ -- ``--file=FILENAME, -f FILENAME`` +``-f, --file`` +^^^^^^^^^^^^^^ + +**Syntax:** ``--file`` FILENAME File to attach, or filename for data provided on stdin -- ``--description=DESCRIPTION, -d DESCRIPTION`` + +``-d, --description`` +^^^^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--description`` DESCRIPTION A short description of the file being attached -- ``--type=MIMETYPE, -t MIMETYPE`` + +``-t, --type`` +^^^^^^^^^^^^^^ + +**Syntax:** ``--type`` MIMETYPE Mime-type for the file being attached -- ``--get=ATTACHID, -g ATTACHID`` + +``-g, --get`` +^^^^^^^^^^^^^ + +**Syntax:** ``--get`` ATTACHID Download the attachment with the given ID -- ``--getall=BUGID, --get-all=BUGID`` + +``--getall`` +^^^^^^^^^^^^ + +**Syntax:** ``--getall`` BUGID Download all attachments on the given bug -- ``--ignore-obsolete`` + +``--ignore-obsolete`` +^^^^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--ignore-obsolete`` Do not download attachments marked as obsolete. -- ``--comment=COMMENT, -l COMMENT`` + +``-l, --comment`` +^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--comment`` COMMENT Add comment with attachment @@ -440,27 +751,86 @@ Add comment with attachment ‘info’ options ============== -- ``--products, -p`` +``-p, --products`` +^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--products`` Get a list of products -- ``--components=PRODUCT, -c PRODUCT`` + +``-c, --components`` +^^^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--components`` PRODUCT List the components in the given product -- ``--component_owners=PRODUCT, -o PRODUCT`` + +``-o, --component_owners`` +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--component_owners`` PRODUCT List components (and their owners) -- ``--versions=PRODUCT, -v PRODUCT`` + +``-v, --versions`` +^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--versions`` PRODUCT List the versions for the given product -- ``--active-components`` + +``--active-components`` +^^^^^^^^^^^^^^^^^^^^^^^ + +**Syntax:** ``--active-components`` Only show active components. Combine with --components* +``bugzillarc`` CONFIG FILE +========================== + +Both ``bugzilla`` and the python-bugzilla library will read +a ``bugzillarc`` config file if it is present in the following +locations: + +- /etc/bugzillarc +- ~/.bugzillarc +- ~/.config/python-bugzilla/bugzillarc + +The contents of the files are processed and merged together +in the order they are listed above. + +The main usage for ``bugzillarc`` is to store API keys for your +bugzilla URLs: + +:: + + [bugzilla.example.com] + api_key=INSERT-YOUR-API-KEY-HERE + + [bugzilla.redhat.com] + api_key=MY-REDHAT-API-KEY-BLAH + + +The sections must be hostnames. Other values that can be +set per hostname section are + +- ``user``: default auth username +- ``password``: default auth password +- ``cert``: default client side certificate + + +A ``[DEFAULTS]`` section is also accepted, which takes the following +values: + +- ``url``: default bugzilla URL + + AUTHENTICATION CACHE AND API KEYS ================================= @@ -472,28 +842,17 @@ active login. If you are connecting to a bugzilla 5.0 or later instance, the best option is to use bugzilla API keys. From the bugzilla web UI, log in, navigate to Preferences->API Keys, and generate a key (it will be a long -string of characters and numbers). Then create a -~/.config/python-bugzilla/bugzillarc like this: +string of characters and numbers). -:: +Then use 'bugzilla --bugzilla URL login --api-key', which will ask +for the API key, and save it to ``bugzillarc`` for you. - $ cat ~/.config/python-bugzilla/bugzillarc - - [bugzilla.example.com] - api_key=YOUR_API_KEY - -Replace 'bugzilla.example.com' with your bugzilla host name, and -YOUR_API_KEY with the generated API Key from the Web UI. - -Alternatively, you can use 'bugzilla login --api-key', which will ask -for the API key, and save it to bugzillarc for you. - -For older bugzilla instances, you will need to cache a login cookie or -token with the "login" subcommand or the "--login" argument. +For older bugzilla instances, you will need to cache a login token +with the "login" subcommand or the "--login" argument. Additionally, the --no-cache-credentials option will tell the bugzilla tool to *not* save or use any authentication cache, including the -bugzillarc file. +``bugzillarc`` file. EXAMPLES @@ -538,4 +897,5 @@ SEE ALSO ======== https://bugzilla.readthedocs.io/en/latest/api/index.html -https://bugzilla.redhat.com/docs/en/html/api/Bugzilla/WebService/Bug.html + +https://bugzilla.redhat.com/docs/en/html/api/core/v1/bug.html diff --git a/python-bugzilla.spec b/python-bugzilla.spec index 404a8a4a..716fc933 100644 --- a/python-bugzilla.spec +++ b/python-bugzilla.spec @@ -1,21 +1,5 @@ -%if 0%{?fedora} || 0%{?rhel} > 7 -# Enable python3 by default -%bcond_without python3 -%else -%bcond_with python3 -%endif - -%if 0%{?fedora} > 31 || 0%{?rhel} > 7 -# Disable python2 build by default -%bcond_with python2 -%else -%bcond_without python2 -%{!?__python2: %global __python2 /usr/bin/python2} -%{!?python2_sitelib: %global python2_sitelib %(%{__python2} -c "from distutils.sysconfig import get_python_lib; print (get_python_lib())")} -%endif - Name: python-bugzilla -Version: 2.5.0 +Version: 3.3.0 Release: 1%{?dist} Summary: Python library for interacting with Bugzilla @@ -24,21 +8,10 @@ URL: https://github.com/python-bugzilla/python-bugzilla Source0: https://github.com/python-bugzilla/python-bugzilla/archive/v%{version}/%{name}-%{version}.tar.gz BuildArch: noarch -%if %{with python2} -BuildRequires: python2-devel -BuildRequires: python2-docutils -BuildRequires: python2-requests -BuildRequires: python2-setuptools -BuildRequires: python2-pytest -%endif - -%if %{with python3} BuildRequires: python3-devel -BuildRequires: python3-docutils BuildRequires: python3-requests BuildRequires: python3-setuptools BuildRequires: python3-pytest -%endif %global _description\ python-bugzilla is a python library for interacting with bugzilla instances\ @@ -47,42 +20,20 @@ over XMLRPC or REST.\ %description %_description -%if %{with python2} -%package -n python2-bugzilla -Summary: %summary -Requires: python2-requests -# This dep is for back compat, so that installing python-bugzilla continues -# to give the cli tool -Requires: python-bugzilla-cli -%{?python_provide:%python_provide python2-bugzilla} - -%description -n python2-bugzilla %_description - -%endif - - -%if %{with python3} %package -n python3-bugzilla Summary: %summary Requires: python3-requests %{?python_provide:%python_provide python3-bugzilla} -%if %{without python2} Obsoletes: python-bugzilla < %{version}-%{release} Obsoletes: python2-bugzilla < %{version}-%{release} -%endif %description -n python3-bugzilla %_description -%endif %package cli Summary: Command line tool for interacting with Bugzilla -%if %{with python3} Requires: python3-bugzilla = %{version}-%{release} -%else -Requires: python2-bugzilla = %{version}-%{release} -%endif %description cli This package includes the 'bugzilla' command-line tool for interacting with bugzilla. Uses the python-bugzilla API @@ -92,65 +43,21 @@ This package includes the 'bugzilla' command-line tool for interacting with bugz %prep %setup -q -%if %{with python3} -rm -rf %{py3dir} -cp -a . %{py3dir} -%endif - %install -%if %{with python3} -pushd %{py3dir} %{__python3} setup.py install -O1 --root %{buildroot} -%if %{with python2} -rm %{buildroot}/usr/bin/bugzilla -%endif - -popd -%endif - -%if %{with python2} -%{__python2} setup.py install -O1 --root %{buildroot} -%endif - -# Replace '#!/usr/bin/env python' with '#!/usr/bin/python2' -# The format is ideal for upstream, but not a distro. See: -# https://fedoraproject.org/wiki/Features/SystemPythonExecutablesUseSystemPython -%if %{with python3} -%global python_env_path %{__python3} -%else -%global python_env_path %{__python2} -%endif -for f in $(find %{buildroot} -type f -executable -print); do - sed -i "1 s|^#!/usr/bin/.*|#!%{python_env_path}|" $f || : -done - %check -%if %{with python2} -# py.test naming is needed for RHEL7 compat, works fine with Fedora -py.test -%endif -%if %{with python3} pytest-3 -%endif - -%if %{with python2} -%files -n python2-bugzilla -%doc COPYING README.md NEWS.md -%{python2_sitelib}/* -%endif -%if %{with python3} %files -n python3-bugzilla %doc COPYING README.md NEWS.md %{python3_sitelib}/* -%endif %files cli %{_bindir}/bugzilla diff --git a/requirements.txt b/requirements.txt index f6bdb7bc..f2293605 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1 @@ requests -docutils diff --git a/setup.py b/setup.py index 2fadede1..b9db594d 100755 --- a/setup.py +++ b/setup.py @@ -1,24 +1,12 @@ #!/usr/bin/env python3 -from __future__ import print_function - import glob import os +import shutil import subprocess import sys -import distutils.command.build -from distutils.core import Command -from setuptools import setup - - -def unsupported_python_version(): - return sys.version_info < (2, 7) \ - or (sys.version_info > (3,) and sys.version_info < (3, 4)) - - -if unsupported_python_version(): - raise ImportError("python-bugzilla does not support this python version") +import setuptools def get_version(): @@ -28,7 +16,7 @@ def get_version(): return eval(line.split('=')[-1]) # pylint: disable=eval-used -class PylintCommand(Command): +class PylintCommand(setuptools.Command): user_options = [] def initialize_options(self): @@ -57,13 +45,13 @@ def run(self): print("running pylint") pylint_opts = [ - "--rcfile", "pylintrc", + "--rcfile", ".pylintrc", "--output-format=%s" % output_format, ] pylint.lint.Run(files + pylint_opts) -class RPMCommand(Command): +class RPMCommand(setuptools.Command): description = ("Build src and binary rpms and output them " "in the source directory") user_options = [] @@ -80,19 +68,27 @@ def run(self): "rpmbuild", "-ta", "--define", "_rpmdir %s" % srcdir, "--define", "_srcrpmdir %s" % srcdir, + "--define", "_specdir /tmp", "dist/python-bugzilla-%s.tar.gz" % get_version(), ] subprocess.check_call(cmd) -class BuildCommand(distutils.command.build.build): +class ManCommand(setuptools.Command): + description = ("Regenerate manpages from rst") + user_options = [] + + def initialize_options(self): + pass + def finalize_options(self): + pass + def _make_man_pages(self): - from distutils.spawn import find_executable - rstbin = find_executable("rst2man") + rstbin = shutil.which("rst2man") if not rstbin: - rstbin = find_executable("rst2man.py") + rstbin = shutil.which("rst2man.py") if not rstbin: - sys.exit("Didn't find rst2man or rst2man.py") + raise RuntimeError("Didn't find rst2man or rst2man.py") for path in glob.glob("man/*.rst"): base = os.path.basename(path) @@ -109,7 +105,6 @@ def _make_man_pages(self): def run(self): self._make_man_pages() - distutils.command.build.build.run(self) def _parse_requirements(fname): @@ -121,7 +116,7 @@ def _parse_requirements(fname): return ret -setup( +setuptools.setup( name='python-bugzilla', version=get_version(), description='Library and command line tool for interacting with Bugzilla', @@ -134,24 +129,23 @@ def _parse_requirements(fname): 'GNU General Public License v2 or later (GPLv2+)', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', ], packages=['bugzilla'], - data_files=[], + data_files=[('share/man/man1', ['man/bugzilla.1'])], entry_points={'console_scripts': ['bugzilla = bugzilla._cli:cli']}, install_requires=_parse_requirements("requirements.txt"), tests_require=_parse_requirements("test-requirements.txt"), cmdclass={ - "build": BuildCommand, + "regenerate_manpages": ManCommand, "pylint": PylintCommand, "rpm": RPMCommand, }, diff --git a/test-requirements.txt b/test-requirements.txt index c588a62a..6e80f2c7 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,2 +1,5 @@ # additional packages needed for testing pytest +pylint<3.1 +pycodestyle<2.12 +responses diff --git a/tests/conftest.py b/tests/conftest.py index 7481f0fc..d398437d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,8 +4,10 @@ import locale import logging import os +import re import pytest +import responses import tests import tests.utils @@ -17,13 +19,17 @@ # https://docs.pytest.org/en/latest/writing_plugins.html def pytest_addoption(parser): + parser.addoption("--ro-integration", action="store_true", default=False, + help="Run readonly tests against local Bugzilla instance.") + parser.addoption("--rw-integration", action="store_true", default=False, + help="Run read-write tests against local Bugzilla instance.") parser.addoption("--ro-functional", action="store_true", default=False, help=("Run readonly functional tests against actual " "bugzilla instances. This will be very slow.")) parser.addoption("--rw-functional", action="store_true", default=False, help=("Run read/write functional tests against actual bugzilla " "instances. As of now this only runs against " - "partner-bugzilla.redhat.com, which requires an RH " + "bugzilla.stage.redhat.com, which requires an RH " "bugzilla account with cached login creds. This will " "also be very slow.")) parser.addoption("--redhat-url", @@ -38,31 +44,40 @@ def pytest_addoption(parser): parser.addoption("--only-xmlrpc", action="store_true", default=False) -def pytest_ignore_collect(path, config): +def pytest_ignore_collect(collection_path, config): has_ro = config.getoption("--ro-functional") + has_ro_i = config.getoption("--ro-integration") has_rw = config.getoption("--rw-functional") - skip_rest = has_ro or has_rw + has_rw_i = config.getoption("--rw-integration") - base = os.path.basename(str(path)) + base = os.path.basename(str(collection_path)) is_ro = base == "test_ro_functional.py" + is_ro_i = "tests/integration/ro" in str(collection_path) is_rw = base == "test_rw_functional.py" - if is_ro or is_rw: - if is_ro and not has_ro: - return True - if is_rw and not has_rw: - return True - elif skip_rest: - config.option.verbose = 2 + is_rw_i = "tests/integration/rw" in str(collection_path) + + if is_ro_i and not has_ro_i: + return True + if is_rw_i and not has_rw_i: + return True + + if is_ro and not has_ro: + return True + if is_rw and not has_rw: return True def pytest_configure(config): - try: - # Needed for test reproducibility on systems not using a UTF-8 locale - locale.setlocale(locale.LC_ALL, 'C') - locale.setlocale(locale.LC_CTYPE, 'en_US.UTF-8') - except Exception as e: - print("Error setting locale: %s" % str(e)) + # Needed for test reproducibility on any system not using a UTF-8 locale + locale.setlocale(locale.LC_ALL, "C") + for loc in ["C.UTF-8", "C.utf8", "UTF-8", "en_US.UTF-8"]: + try: + locale.setlocale(locale.LC_CTYPE, loc) + break + except locale.Error: + pass + else: + raise locale.Error("No UTF-8 locale found") if config.getoption("--redhat-url"): tests.CLICONFIG.REDHAT_URL = config.getoption("--redhat-url") @@ -72,16 +87,15 @@ def pytest_configure(config): if config.getoption("--regenerate-output"): tests.CLICONFIG.REGENERATE_OUTPUT = config.getoption( "--regenerate-output") - if not (config.getoption("--ro-functional") or - config.getoption("--rw-functional")): - # Functional tests need access to HOME cached auth. - # Unit tests shouldn't be touching any HOME files - os.environ["HOME"] = os.path.dirname(__file__) + "/data/homedir" if config.getoption("--only-rest"): tests.CLICONFIG.ONLY_REST = True if config.getoption("--only-xmlrpc"): tests.CLICONFIG.ONLY_XMLRPC = True + if (config.getoption("--ro-functional") or + config.getoption("--rw-functional")): + config.option.verbose = 2 + def pytest_generate_tests(metafunc): """ @@ -109,3 +123,47 @@ def run_cli(capsys, monkeypatch): def _do_run(*args, **kwargs): return tests.utils.do_run_cli(capsys, monkeypatch, *args, **kwargs) return _do_run + + +@pytest.fixture +def mocked_responses(): + """ + Mock responses + + * Quickly return error responses + * Pass through requests to live instances + * Provide an incorrect XMLRPC response + """ + passthrough = () + status_pattern = re.compile(r"https://httpstat.us/(?P\d+).*") + + def status_callback(request): + match = status_pattern.match(request.url) + status_code = 400 + if match: + status_code = int(match.group("status")) + + return status_code, {}, "

Lorem ipsum

" + + test_url = os.getenv("BUGZILLA_URL") + if test_url: + passthrough += (test_url, test_url.replace("http://", "https://")) + with responses.RequestsMock(passthru_prefixes=passthrough, + assert_all_requests_are_fired=False) as mock: + mock.add_callback( + method=responses.GET, + url=status_pattern, + callback=status_callback + ) + mock.add_callback( + method=responses.POST, + url=status_pattern, + callback=status_callback + ) + mock.add( + method=responses.POST, + url="https://example.com/#xmlrpc", + status=200, + body="This is no XML" + ) + yield mock diff --git a/tests/data/authfiles/output-cookies.txt b/tests/data/authfiles/output-cookies.txt deleted file mode 100644 index 193bd5d5..00000000 --- a/tests/data/authfiles/output-cookies.txt +++ /dev/null @@ -1,6 +0,0 @@ -# Netscape HTTP Cookie File -# http://curl.haxx.se/rfc/cookie_spec.html -# This is a generated file! Do not edit. - -.partner-bugzilla.redhat.com TRUE / FALSE 2145916800 Bugzilla_login notacookie -.partner-bugzilla.redhat.com TRUE / FALSE 2145916800 Bugzilla_logincookie notacookie diff --git a/tests/data/clioutput/test_new2.txt b/tests/data/clioutput/test_new2.txt new file mode 100644 index 00000000..0e1e2bde --- /dev/null +++ b/tests/data/clioutput/test_new2.txt @@ -0,0 +1 @@ +#1694158 CLOSED - crobinso@redhat.com - python-bugzilla test bug for API minor_update diff --git a/tests/data/clioutput/test_query10.txt b/tests/data/clioutput/test_query10.txt new file mode 100644 index 00000000..76cb0117 --- /dev/null +++ b/tests/data/clioutput/test_query10.txt @@ -0,0 +1 @@ +#1165434 CLOSED - lvm-team@redhat.com - LVM mirrored root can deadlock dmeventd if a mirror leg is lost diff --git a/tests/data/clioutput/test_query2.txt b/tests/data/clioutput/test_query2.txt index f7a3b723..d959145f 100644 --- a/tests/data/clioutput/test_query2.txt +++ b/tests/data/clioutput/test_query2.txt @@ -57,7 +57,7 @@ ATTRIBUTE[target_milestone]: rc ATTRIBUTE[target_release]: ['---'] ATTRIBUTE[url]: ATTRIBUTE[version]: ['5.8'] -ATTRIBUTE[weburl]: https:///TESTSUITEMOCK +ATTRIBUTE[weburl]: https:///show_bug.cgi?id=1165434 ATTRIBUTE[whiteboard]: genericwhiteboard diff --git a/tests/data/clioutput/tokenfile.txt b/tests/data/clioutput/tokenfile.txt new file mode 100644 index 00000000..3f4ff578 --- /dev/null +++ b/tests/data/clioutput/tokenfile.txt @@ -0,0 +1,3 @@ +[example.com] +token = my-fake-token + diff --git a/tests/data/cookies-bad.txt b/tests/data/cookies-bad.txt deleted file mode 100644 index 0928f036..00000000 --- a/tests/data/cookies-bad.txt +++ /dev/null @@ -1 +0,0 @@ -foo this is invalid cookies diff --git a/tests/data/cookies-lwp.txt b/tests/data/cookies-lwp.txt deleted file mode 100644 index b8818ef3..00000000 --- a/tests/data/cookies-lwp.txt +++ /dev/null @@ -1,3 +0,0 @@ -#LWP-Cookies-2.0 -Set-Cookie3: Bugzilla_login=notacookie; path="/"; domain=".partner-bugzilla.redhat.com"; domain_dot; expires="2038-01-01 00:00:00Z"; version=0 -Set-Cookie3: Bugzilla_logincookie=notacookie; path="/"; domain=".partner-bugzilla.redhat.com"; domain_dot; expires="2038-01-01 00:00:00Z"; version=0 diff --git a/tests/data/cookies-moz.txt b/tests/data/cookies-moz.txt deleted file mode 100644 index 6a16c9db..00000000 --- a/tests/data/cookies-moz.txt +++ /dev/null @@ -1,6 +0,0 @@ -# Netscape HTTP Cookie File -# http://www.netscape.com/newsref/std/cookie_spec.html -# This is a generated file! Do not edit. - -.partner-bugzilla.redhat.com TRUE / FALSE 2145916800 Bugzilla_login notacookie -.partner-bugzilla.redhat.com TRUE / FALSE 2145916800 Bugzilla_logincookie notacookie diff --git a/tests/data/mockargs/test_attach3.txt b/tests/data/mockargs/test_attach3.txt new file mode 100644 index 00000000..48da7d7a --- /dev/null +++ b/tests/data/mockargs/test_attach3.txt @@ -0,0 +1,9 @@ +(['123456'], + 'STRIPPED-BY-TESTSUITE', + {'content_type': 'text/plain', + 'file_name': 'bz-attach-get1.txt', + 'flags': [{'name': 'review', + 'requestee': 'crobinso@redhat.com', + 'status': '-'}], + 'is_obsolete': '1', + 'summary': 'bz-attach-get1.txt'}) diff --git a/tests/data/mockargs/test_modify2.txt b/tests/data/mockargs/test_modify2.txt index 5ee27d14..3c0315af 100644 --- a/tests/data/mockargs/test_modify2.txt +++ b/tests/data/mockargs/test_modify2.txt @@ -1,5 +1,5 @@ (['123456'], - {'blocks': {'set': [123456, 445566]}, + {'blocks': {'set': ['123456', '445566']}, 'comment': {'comment': 'some example comment', 'is_private': True}, 'component': 'NEWCOMP', 'dupe_of': 555666, diff --git a/tests/data/mockargs/test_modify5.txt b/tests/data/mockargs/test_modify5.txt new file mode 100644 index 00000000..b21a6ee2 --- /dev/null +++ b/tests/data/mockargs/test_modify5.txt @@ -0,0 +1,29 @@ +(['1165434'], + {'alias': 'fooalias', + 'assigned_to': 'foo@example.com', + 'bar': 'foo', + 'blocks': {'add': ['1234'], 'remove': ['1235'], 'set': []}, + 'cc': {'add': ['+bar@example.com'], 'remove': ['steve@example.com']}, + 'cf_blah': {'1': 2}, + 'cf_devel_whiteboard': 'DEVBOARD', + 'cf_internal_whiteboard': 'INTBOARD', + 'cf_qa_whiteboard': 'QABOARD', + 'cf_verified': ['Tested'], + 'comment_tags': ['FOOTAG'], + 'depends_on': {'add': ['2234'], 'remove': ['2235'], 'set': []}, + 'groups': {'add': ['foogroup']}, + 'keywords': {'add': ['newkeyword'], 'remove': ['byekeyword'], 'set': []}, + 'minor_update': True, + 'op_sys': 'windows', + 'platform': 'mips', + 'priority': 'high', + 'product': 'newproduct', + 'qa_contact': 'qa@example.com', + 'reset_assigned_to': True, + 'reset_qa_contact': True, + 'severity': 'low', + 'summary': 'newsummary', + 'target_milestone': 'beta', + 'target_release': '1.2.4', + 'url': 'https://example.com', + 'version': '1.2.3'}) diff --git a/tests/data/mockargs/test_new2.txt b/tests/data/mockargs/test_new2.txt new file mode 100644 index 00000000..a3636ead --- /dev/null +++ b/tests/data/mockargs/test_new2.txt @@ -0,0 +1,24 @@ +{'alias': 'somealias', + 'assigned_to': 'foo@example.com', + 'blocks': ['12345', '6789'], + 'cc': ['foo@example.com', 'bar@example.com'], + 'cf_blah': {'1': 2}, + 'cf_verified': ['Tested'], + 'comment_is_private': True, + 'comment_tags': ['FOO'], + 'component': 'FOOCOMP', + 'depends_on': ['dependme'], + 'description': 'This is the first comment!\nWith newline & stuff.', + 'foo': 'bar', + 'groups': ['FOOGROUP', 'BARGROUP'], + 'keywords': ['ADDKEY'], + 'op_sys': 'linux', + 'platform': 'mips', + 'priority': 'low', + 'product': 'FOOPROD', + 'qa_contact': 'qa@example.com', + 'severity': 'high', + 'sub_components': {'FOOCOMP': ['FOOCOMP']}, + 'summary': 'Hey this is the title!', + 'url': 'https://some.example.com', + 'version': '5.6.7'} diff --git a/tests/data/mockargs/test_query10.txt b/tests/data/mockargs/test_query10.txt new file mode 100644 index 00000000..42ae8b14 --- /dev/null +++ b/tests/data/mockargs/test_query10.txt @@ -0,0 +1,39 @@ +{'alias': 'somealias', + 'assigned_to': 'bar@example.com', + 'field0-0-0': 'keywords', + 'field1-0-0': 'blocked', + 'field2-0-0': 'dependson', + 'field3-0-0': 'bug_file_loc', + 'field4-0-0': 'cf_fixed_in', + 'field5-0-0': 'flagtypes.name', + 'field6-0-0': 'status_whiteboard', + 'field7-0-0': 'cf_devel_whiteboard', + 'include_fields': ['assigned_to', 'id', 'status', 'summary'], + 'priority': ['wibble'], + 'query_format': 'advanced', + 'quicksearch': '1', + 'reporter': 'me@example.com', + 'savedsearch': '2', + 'sharer_id': '3', + 'short_desc': 'search summary', + 'sub_components': ['FOOCOMP'], + 'tag': ['+foo'], + 'target_milestone': 'bar', + 'target_release': 'foo', + 'type0-0-0': 'substring', + 'type1-0-0': 'substring', + 'type2-0-0': 'substring', + 'type3-0-0': 'sometype', + 'type4-0-0': 'substring', + 'type5-0-0': 'substring', + 'type6-0-0': 'substring', + 'type7-0-0': 'substring', + 'value0-0-0': 'FOO', + 'value1-0-0': '12345', + 'value2-0-0': '23456', + 'value3-0-0': 'https://example.com', + 'value4-0-0': '5.5.5', + 'value5-0-0': 'needinfo', + 'value6-0-0': 'FOO', + 'value7-0-0': 'DEVBOARD', + 'version': ['5.6.7']} diff --git a/tests/data/mockargs/test_query6.txt b/tests/data/mockargs/test_query6.txt index 78b18f48..b3a21252 100644 --- a/tests/data/mockargs/test_query6.txt +++ b/tests/data/mockargs/test_query6.txt @@ -1,6 +1,8 @@ {'BAR': 'WIBBLE', 'FOO': '1', 'bug_status': ['VERIFIED', 'RELEASE_PENDING', 'CLOSED'], + 'cf_blah': {'1': 2}, + 'cf_verified': ['Tested'], 'include_fields': ['assigned_to', 'blocks', 'component', diff --git a/tests/data/mockreturn/test_query1.txt b/tests/data/mockreturn/test_query1.txt index d1bdac5c..4f7019b2 100644 --- a/tests/data/mockreturn/test_query1.txt +++ b/tests/data/mockreturn/test_query1.txt @@ -1 +1 @@ -{'bugs': [{'assigned_to_detail': {'real_name': 'Libvirt Maintainers', 'email': 'libvirt-maint', 'name': 'libvirt-maint', 'id': 311982}, 'summary': 'RFE: qemu: Support a managed autoconnect mode for host USB devices', 'status': 'NEW', 'assigned_to': 'Libvirt Maintainers', 'id': 508645}, {'assigned_to_detail': {'real_name': 'Cole Robinson', 'email': 'crobinso', 'name': 'crobinso', 'id': 199727}, 'summary': 'RFE: warn users at guest start if networks/storage pools are inactive', 'status': 'NEW', 'assigned_to': 'Cole Robinson', 'id': 668543}]} +{'bugs': [{'assigned_to_detail': {'real_name': 'Libvirt Maintainers', 'email': 'libvirt-maint', 'name': 'libvirt-maint', 'id': 311982}, 'summary': 'RFE: qemu: Support a managed autoconnect mode for host USB devices', 'status': 'NEW', 'assigned_to': 'Libvirt Maintainers', 'id': 508645}, {'assigned_to_detail': {'real_name': 'Cole Robinson', 'email': 'crobinso', 'name': 'crobinso', 'id': 199727}, 'summary': 'RFE: warn users at guest start if networks/storage pools are inactive', 'status': 'NEW', 'assigned_to': 'Cole Robinson', 'id': 668543}], 'limit': 0, 'FOOFAKEVALUE': 'hello'} diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 00000000..908a1f54 --- /dev/null +++ b/tests/integration/__init__.py @@ -0,0 +1,9 @@ +import os + + +TEST_URL = os.getenv("BUGZILLA_URL", "http://localhost") +TEST_OWNER = "andreas@hasenkopf.xyz" +TEST_PRODUCTS = {"Red Hat Enterprise Linux 9", + "SUSE Linux Enterprise Server 15 SP6", + "TestProduct"} +TEST_SUSE_COMPONENTS = {"Containers", "Kernel"} diff --git a/tests/integration/ro_api_test.py b/tests/integration/ro_api_test.py new file mode 100644 index 00000000..d3d6b526 --- /dev/null +++ b/tests/integration/ro_api_test.py @@ -0,0 +1,238 @@ +# Ignoring pytest-related warnings: +# pylint: disable=redefined-outer-name,unused-argument +from urllib.parse import urljoin +from xmlrpc.client import Fault + +import pytest + +from bugzilla import BugzillaError + +from ..utils import open_bz +from . import TEST_URL, TEST_PRODUCTS, TEST_SUSE_COMPONENTS, TEST_OWNER + + +def test_rest_xmlrpc_detection(mocked_responses): + # The default: use XMLRPC + bz = open_bz(url=TEST_URL) + assert bz.is_xmlrpc() + assert "/xmlrpc.cgi" in bz.url + + # See /rest in the URL, so use REST + bz = open_bz(url=TEST_URL + "/rest") + assert bz.is_rest() + with pytest.raises(BugzillaError) as e: + dummy = bz._proxy # pylint: disable=protected-access + assert "raw XMLRPC access is not provided" in str(e) + + # See /xmlrpc.cgi in the URL, so use XMLRPC + bz = open_bz(url=TEST_URL + "/xmlrpc.cgi") + assert "/xmlrpc.cgi" in bz.url + assert bz.is_xmlrpc() + assert bz._proxy # pylint: disable=protected-access + + +def test_apikey_error_scraping(mocked_responses): + # Ensure the API key does not leak into any requests exceptions + fakekey = "FOOBARMYKEY" + with pytest.raises(Exception) as e: + open_bz("https://httpstat.us/400&foo", + force_xmlrpc=True, api_key=fakekey) + assert "Client Error" in str(e.value) + assert fakekey not in str(e.value) + + with pytest.raises(Exception) as e: + open_bz("https://httpstat.us/400&foo", + force_rest=True, api_key=fakekey) + assert "Client Error" in str(e.value) + assert fakekey not in str(e.value) + + +def test_xmlrpc_bad_url(mocked_responses): + with pytest.raises(BugzillaError) as e: + open_bz(url="https://example.com/#xmlrpc", force_xmlrpc=True) + assert "URL may not be an XMLRPC URL" in str(e) + + +def test_get_products(mocked_responses, backends): + bz = open_bz(url=TEST_URL, **backends) + + assert len(bz.products) == 3 + assert {p["name"] for p in bz.products} == TEST_PRODUCTS + + rhel = next(p for p in bz.products if p["id"] == 2) + assert {v["name"] for v in rhel["versions"]} == {"9.0", "9.1", "unspecified"} + + +def test_get_product(mocked_responses, backends): + bz = open_bz(url=TEST_URL, **backends) + + product_ids = {product["id"] for product in bz.product_get(ptype="enterable", + include_fields=["id"])} + product_names = {product["name"] for product in bz.product_get(ptype="selectable", + include_fields=["name"])} + assert product_ids == {1, 2, 3} + assert product_names == {'Red Hat Enterprise Linux 9', 'SUSE Linux Enterprise Server 15 SP6', + 'TestProduct'} + + +def test_get_components(mocked_responses, backends): + bz = open_bz(url=TEST_URL, **backends) + components = bz.getcomponents(product="SUSE Linux Enterprise Server 15 SP6") + assert len(components) == 2 + assert set(components) == TEST_SUSE_COMPONENTS + + +def test_get_component_detail(mocked_responses, backends): + bz = open_bz(url=TEST_URL, **backends) + component = bz.getcomponentdetails(product="Red Hat Enterprise Linux 9", + component="python-bugzilla") + assert component["id"] == 2 + assert component["default_assigned_to"] == TEST_OWNER + + +def test_query(mocked_responses, backends): + bz = open_bz(url=TEST_URL, **backends) + query = bz.build_query(product="Red Hat Enterprise Linux 9", component="python-bugzilla") + bugs = bz.query(query=query) + + assert len(bugs) == 1 + assert bugs[0].id == 2 + assert bugs[0].summary == "Expect the Spanish inquisition" + + bz = open_bz(url=TEST_URL, **backends) + query = bz.build_query(product="SUSE Linux Enterprise Server 15 SP6", component="Containers") + bugs = bz.query(query=query) + + assert len(bugs) == 1 + assert bugs[0].id == 1 + assert bugs[0].whiteboard == "AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:L" + + +def test_get_bug_alias(mocked_responses, backends): + bug_id, alias = 1, "FOO-1" + bz = open_bz(url=TEST_URL, **backends) + bug = bz.getbug(alias) + + assert bug.id == bug_id + assert bug.bug_id == bug_id + assert bug.alias == [alias] + assert bug.summary == "ZeroDivisionError in function foo_bar()" + + +def test_bug_url(mocked_responses, backends): + bz = open_bz(url=TEST_URL, **backends) + bug_id = 2 + + # Ensure weburl is generated consistently whether + # we are using XMLRPC or REST + bug = bz.getbug(bug_id) + assert bug.weburl == urljoin(TEST_URL, f"/show_bug.cgi?id={bug_id}") + + +def test_get_bug_alias_included_field(mocked_responses, backends): + bug_id, alias = 1, "FOO-1" + bz = open_bz(url=TEST_URL, **backends) + bug = bz.getbug(alias, include_fields=["id"]) + + assert bug.id == bug_id + assert bug.bug_id == bug_id + assert bug.alias == [alias] + assert not hasattr(bug, "summary") + + +def test_get_bug_exclude_fields(mocked_responses, backends): + bz = open_bz(url=TEST_URL, **backends) + + # Check default extra_fields will pull in comments + bug = bz.getbug(2, exclude_fields=["product"]) + assert not hasattr(bug, "product") + + # Ensure that include_fields overrides default extra_fields + bug = bz.getbug(2) + assert hasattr(bug, "product") + + +def test_get_bug_404(mocked_responses, backends): + bz = open_bz(url=TEST_URL, **backends) + try: + bz.getbug(666) + except Fault as error: # XMLRPC API + assert error.faultCode == 101 + except BugzillaError as error: # REST API + assert error.code == 101 + else: + raise AssertionError("No exception raised") + + +def test_get_bug_alias_404(mocked_responses, backends): + bz = open_bz(url=TEST_URL, **backends) + try: + bz.getbug("CVE-1234-4321") + except Fault as error: # XMLRPC API + assert error.faultCode == 100 + except BugzillaError as error: # REST API + assert error.code == 100 + else: + raise AssertionError("No exception raised") + + +def test_get_bug_fields(mocked_responses, backends): + bz = open_bz(url=TEST_URL, **backends) + fields = bz.getbugfields(names=["product"]) + assert fields == ["product"] + bz.getbugfields(names=["product", "bug_status"], force_refresh=True) + assert set(bz.bugfields) == {"product", "bug_status"} + + +def test_query_autorefresh(mocked_responses, backends): + bz = open_bz(url=TEST_URL, **backends) + + bz.bug_autorefresh = True + bug = bz.query(bz.build_query(bug_id=1, include_fields=["summary"]))[0] + assert hasattr(bug, "component") + assert bool(bug.component) + + bz.bug_autorefresh = False + bug = bz.query(bz.build_query(bug_id=1, include_fields=["summary"]))[0] + assert not hasattr(bug, "component") + try: + assert bool(bug.component) + except Exception as e: + assert "adjust your include_fields" in str(e) + + +def test_logged_in_no_creds(mocked_responses, backends): + bz = open_bz(url=TEST_URL, use_creds=False, **backends) + assert not bz.logged_in + + +def test_login_stubs(mocked_responses, backends): + # Explicitly set configpaths to avoid interference with an API key set by another test + bz = open_bz(url=TEST_URL, configpaths="/dev/null", **backends) + bz_apikey = open_bz(url=TEST_URL, api_key="random-and-secure-api-key", **backends) + + # Failed login, verifies our backends are calling the correct API + with pytest.raises(BugzillaError) as e: + bz.login("foo", "bar") + assert "Login failed" in str(e) + + # Login is prohibited, when an API key is defined + with pytest.raises(ValueError) as e: + bz_apikey.login("foo", "bar") + assert "cannot login when using an API key" in str(e) + + # Works fine when not logged in + bz.logout() + + +def test_query_resolution(mocked_responses, backends): + bz = open_bz(url=TEST_URL, **backends) + + bugs = bz.query(bz.build_query(short_desc="ZeroDivisionError", resolution=None)) + assert len(bugs) == 1 + + bugs = bz.query(bz.build_query(short_desc="ZeroDivisionError", resolution="---")) + assert len(bugs) == 1 + + bugs = bz.query(bz.build_query(short_desc="ZeroDivisionError", resolution="DUPLICATE")) + assert len(bugs) == 0 diff --git a/tests/integration/ro_cli_test.py b/tests/integration/ro_cli_test.py new file mode 100644 index 00000000..9cd5b3dd --- /dev/null +++ b/tests/integration/ro_cli_test.py @@ -0,0 +1,134 @@ +# Ignoring pytest-related warnings: +# pylint: disable=unused-argument +import re +from urllib.parse import urljoin, urlparse, urlunparse + +from ..utils import open_bz +from . import TEST_URL, TEST_PRODUCTS, TEST_SUSE_COMPONENTS, TEST_OWNER + + +def test_fails(mocked_responses, run_cli, backends): + bz = open_bz(url=TEST_URL, **backends) + out = run_cli("bugzilla query --field=IDONTEXIST=FOO", bzinstance=bz, expectfail=True) + assert "Server error:" in out + + out = run_cli("bugzilla --bugzilla https://example.com/xmlrpc.cgi query --field=IDONTEXIST=FOO", + bzinstance=None, expectfail=True) + assert "Connection lost/failed" in out + + parsed = urlparse(TEST_URL) + netloc = parsed.netloc + if not re.search(r":\d+$", netloc): + netloc += ":80" + + https_test_url = urlunparse(("https", netloc, parsed.path, parsed.params, parsed.query, + parsed.fragment)) + out = run_cli(f"bugzilla --bugzilla {https_test_url} query --bug_id 1234", + bzinstance=None, expectfail=True) + assert "trust the remote server" in out + assert "--nosslverify" in out + + +def test_get_products(mocked_responses, run_cli, backends): + bz = open_bz(url=TEST_URL, **backends) + out = run_cli("bugzilla info --products", bzinstance=bz) + assert len(out.strip().split("\n")) == 3 + + for product in TEST_PRODUCTS: + assert product in out + + +def test_get_components(mocked_responses, run_cli, backends): + bz = open_bz(url=TEST_URL, **backends) + out = run_cli("bugzilla info --components 'SUSE Linux Enterprise Server 15 SP6'", bzinstance=bz) + assert len(out.strip().split("\n")) == 2 + for comp in TEST_SUSE_COMPONENTS: + assert comp in out + + +def test_get_active_components(mocked_responses, run_cli, backends): + bz = open_bz(url=TEST_URL, **backends) + out = run_cli("bugzilla info --components 'SUSE Linux Enterprise Server 15 SP6' " + "--active-components", bzinstance=bz) + assert "Containers" in out + assert "Kernel" in out + + +def test_get_component_owners(mocked_responses, run_cli, backends): + bz = open_bz(url=TEST_URL, **backends) + out = run_cli("bugzilla info --component_owners 'SUSE Linux Enterprise Server 15 SP6'", + bzinstance=bz) + assert TEST_OWNER in out + + +def test_get_versions(mocked_responses, run_cli, backends): + bz = open_bz(url=TEST_URL, **backends) + out = run_cli("bugzilla info --versions 'Red Hat Enterprise Linux 9'", bzinstance=bz) + versions = set(out.strip().split("\n")) + + assert versions == {"unspecified", "9.0", "9.1"} + + +def test_query(mocked_responses, run_cli, backends): + bz = open_bz(url=TEST_URL, **backends) + out = run_cli("bugzilla query --product 'Red Hat Enterprise Linux 9' " + "--component 'python-bugzilla'", bzinstance=bz) + lines = out.strip().splitlines() + + assert len(lines) == 1 + assert lines[0].startswith("#2") + assert "Expect the Spanish inquisition" in lines[0] + + +def test_query_full(mocked_responses, run_cli, backends): + bz = open_bz(url=TEST_URL, **backends) + out = run_cli("bugzilla query --full --bug_id 2", bzinstance=bz) + lines = out.strip().splitlines() + assert len(lines) == 5 + + for name in ('Component', 'CC', 'Blocked', 'Depends'): + assert name in out + + assert "Status Whiteboard" not in out + + +def test_query_raw(mocked_responses, run_cli, backends): + bz = open_bz(url=TEST_URL, **backends) + out = run_cli("bugzilla query --raw --bug_id 2", bzinstance=bz) + + assert "ATTRIBUTE[whiteboard]: lorem ipsum" in out + assert "ATTRIBUTE[id]: 2" in out + + +def test_query_oneline(mocked_responses, run_cli, backends): + bz = open_bz(url=TEST_URL, **backends) + out = run_cli("bugzilla query --oneline --bug_id 2", bzinstance=bz) + lines = out.strip().splitlines() + assert len(lines) == 1 + assert "python-bugzilla" in lines[0] + + +def test_query_extra(mocked_responses, run_cli, backends): + bz = open_bz(url=TEST_URL, **backends) + out = run_cli("bugzilla query --extra --bug_id 2", bzinstance=bz) + lines = out.strip().splitlines() + assert len(lines) == 5 + assert "Keywords: FooBar" in out + assert "Status Whiteboard: lorem ipsum" in out + + +def test_query_format(mocked_responses, run_cli, backends): + bz = open_bz(url=TEST_URL, **backends) + out = run_cli("bugzilla query --outputformat=\"id=%{bug_id} " + "sw=%{whiteboard:status} needinfo=%{flag:needinfo} " + "sum=%{summary}\" --bug_id 2", bzinstance=bz) + lines = out.strip().splitlines() + assert len(lines) == 1 + assert out.strip() == "id=2 sw=lorem ipsum needinfo=? sum=Expect the Spanish inquisition" + + +def test_query_url(mocked_responses, run_cli, backends): + url = urljoin(TEST_URL, "/buglist.cgi?version=9.1") + bz = open_bz(url=TEST_URL, **backends) + out = run_cli(f"bugzilla query --from-url \"{url}\"", bzinstance=bz) + assert re.search(r"#2\s+CONFIRMED", out) diff --git a/tests/integration/rw_api_test.py b/tests/integration/rw_api_test.py new file mode 100644 index 00000000..8fba7dfb --- /dev/null +++ b/tests/integration/rw_api_test.py @@ -0,0 +1,120 @@ +# pylint: disable=unused-argument +from uuid import uuid4 +from xmlrpc.client import Fault + +from pytest import raises +from pytest import mark + +from bugzilla import Bugzilla, BugzillaError +from bugzilla.bug import Bug + +from ..utils import open_bz +from . import TEST_URL + +# NOTE: The tests in this file assume that an API key is defined in the bugzillarc! + + +DEFAULT_PARAMS = {"product": "TestProduct", + "component": "TestComponent", + "version": "unspecified", + "summary": "A new bug", + "description": "Details on how to reproduce", + "cc": "nemo@example.com", + "op_sys": "Linux", + "platform": "PC"} + + +def _create_bug(bz: Bugzilla, **kwargs) -> Bug: + """ + Create a new bug with overwrite-able defaults + """ + params = DEFAULT_PARAMS.copy() + params.update(kwargs) + + return bz.createbug(**bz.build_createbug(**params)) + + +def test_create_bug(mocked_responses, backends): + bz = open_bz(url=TEST_URL, **backends) + bug = _create_bug(bz) + + assert isinstance(bug, Bug) + assert bug.id + + bug = bz.getbug(bug.id) + for field in ("product", "component", "version", "summary"): + assert getattr(bug, field) == DEFAULT_PARAMS[field] + + +def test_create_bug_anonymous(mocked_responses, backends): + bz = open_bz(url=TEST_URL, configpaths="/dev/null", **backends) + with raises((Fault, BugzillaError)): + _create_bug(bz) + + +def test_create_bug_alias(mocked_responses, backends): + bz = open_bz(url=TEST_URL, **backends) + alias = uuid4().hex + bug = _create_bug(bz, alias=alias) + + bug = bz.getbug(bug.id) + assert alias in bug.alias + + with raises((Fault, BugzillaError)): + _create_bug(bz, alias=alias) + + +def test_update_bug(mocked_responses, backends): + email = "nemo@example.com" + bz = open_bz(url=TEST_URL, **backends) + bug = _create_bug(bz) + params = bz.build_update(resolution="WONTFIX", status="RESOLVED", cc_remove=email) + bz.update_bugs(bug.id, params) + bug.refresh() + + assert bug.resolution == "WONTFIX" + assert bug.status == "RESOLVED" + assert bug.cc == [] + + params = bz.build_update(cc_add=email) + bz.update_bugs(bug.id, params) + bug.refresh() + + assert bug.cc == [email] + + +# Bugzilla instance has no CLOSED status +@mark.xfail +def test_close_bug(mocked_responses, backends): + bz = open_bz(url=TEST_URL, **backends) + bug = _create_bug(bz) + bug.close(resolution="WORKSFORME", comment="Bla bla", isprivate=True) + bug.refresh() + + assert bug.resolution == "WORKSFORME" + assert bug.status == "CLOSED" + + +def test_add_comment(mocked_responses, backends): + bz = open_bz(url=TEST_URL, **backends) + bug = bz.getbug(1) + + comment_count = len(bug.get_comments()) + bug.addcomment("Bla Bla bla", private=True) + bug.refresh() + + assert len(bug.get_comments()) == comment_count + 1 + + +def test_update_flags(mocked_responses, backends): + bz = open_bz(url=TEST_URL, **backends) + bug = _create_bug(bz) + flag = {"requestee": "nemo@example.com", "name": "needinfo", "status": "?"} + params = bz.build_update(flags=[flag]) + bz.update_bugs([bug.id], params) + bug.refresh() + + assert len(bug.flags) == 1 + + for key, value in flag.items(): + assert bug.flags[0][key] == value diff --git a/tests/services/Dockerfile b/tests/services/Dockerfile new file mode 100644 index 00000000..83c1e209 --- /dev/null +++ b/tests/services/Dockerfile @@ -0,0 +1,28 @@ +FROM ubuntu:22.04 +LABEL description="Bugzilla image for testing purposes" +ARG DEBIAN_FRONTEND=noninteractive +ENV TZ="Etc/UTC" +RUN apt update && \ + apt install --no-install-recommends -q -y \ + tzdata wget apache2 libcgi-pm-perl libdatetime-perl libdatetime-timezone-perl libdbi-perl \ + libdbix-connector-perl libdigest-sha-perl libemail-address-perl libemail-mime-perl \ + libemail-sender-perl libjson-xs-perl liblist-moreutils-perl libmath-random-isaac-perl \ + libtemplate-perl libtimedate-perl liburi-perl libmariadb-dev-compat libdbd-mysql-perl \ + libxmlrpc-lite-perl libsoap-lite-perl libapache2-mod-perl2 libtest-taint-perl \ + libjson-rpc-perl && \ + apt clean +RUN mkdir -p /var/www/webapps && \ + wget https://ftp.mozilla.org/pub/mozilla.org/webtools/bugzilla-5.0.6.tar.gz \ + -O /tmp/bugzilla-5.0.6.tar.gz&& \ + tar xvzf /tmp/bugzilla-5.0.6.tar.gz && \ + rm /tmp/bugzilla-5.0.6.tar.gz && \ + mv /bugzilla-5.0.6/ /var/www/webapps/bugzilla/ && \ + mkdir /var/www/webapps/bugzilla/data/ +COPY bugzilla.conf /etc/apache2/sites-available/ +COPY localconfig /var/www/webapps/bugzilla/ +COPY params.json /var/www/webapps/bugzilla/data/ +RUN a2dissite 000-default && \ + a2ensite bugzilla && \ + a2enmod cgi headers expires rewrite perl && \ + /var/www/webapps/bugzilla/checksetup.pl +CMD apachectl -D FOREGROUND diff --git a/tests/services/README.md b/tests/services/README.md new file mode 100644 index 00000000..c59174f3 --- /dev/null +++ b/tests/services/README.md @@ -0,0 +1,58 @@ +# Working with the containerized Bugzilla instance + +This document describes the steps for building a Bugzilla container image that can be used in the +GitHub Actions as a service and generating a database dump. + +In the following examples, the use of `docker` is assumed. Commands for `podman` should be +identical. + +## Build + +```shell +$ docker network create --driver bridge local-bridge +$ docker run --rm -itd \ + --env MARIADB_USER=bugs \ + --env MARIADB_DATABASE=bugs \ + --env MARIADB_PASSWORD=secret \ + --env MARIADB_ROOT_PASSWORD=supersecret \ + -p 3306:3306 \ + --network local-bridge \ + --name mariadb \ + mariadb:latest +$ mariadb -u bugs -h 127.0.0.1 -P 3306 --password=secret bugs < bugs.sql +$ docker build --network local-bridge . -t ghcr.io/crazyscientist/bugzilla:test +``` + +For those, who can spot the _chicken and egg problem_: The first version of `bugs.sql` was +created after running the Bugzilla installer inside the container. + +## Usage + +Once built, you can follow the above instructions; instead of building +the image, you can run it: + +```shell +docker run --rm -itd \ + -p 8000:80 \ + --network local-bridge \ + ghcr.io/crazyscientist/bugzilla:test +``` + +## Test data + +The test data used by the Bugzilla service in the integration test suite is stored in `bugs.sql`. + +One can edit this file manually or follow the above instructions to start both a MariaDB and +Bugzilla container and edit the data in Bugzilla. Once done, one needs to dump the changed data into +the file again: + +```shell +$ mariadb-dump -u bugs -h 127.0.0.1 -P 3306 --password=secret bugs > bugs.sql +``` + +## Testing +And now, you can run the integration tests against this instance: + +```shell +BUGZILLA_URL=http://localhost:8000 pytest --ro-integration +``` diff --git a/tests/services/bugs.sql b/tests/services/bugs.sql new file mode 100644 index 00000000..15c78d34 --- /dev/null +++ b/tests/services/bugs.sql @@ -0,0 +1,2277 @@ +/*!999999\- enable the sandbox mode */ +-- MariaDB dump 10.19 Distrib 10.6.18-MariaDB, for debian-linux-gnu (x86_64) +-- +-- Host: 127.0.0.1 Database: bugs +-- ------------------------------------------------------ +-- Server version 11.1.2-MariaDB-1:11.1.2+maria~ubu2204 + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `attach_data` +-- + +DROP TABLE IF EXISTS `attach_data`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `attach_data` ( + `id` mediumint(9) NOT NULL, + `thedata` longblob NOT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `fk_attach_data_id_attachments_attach_id` FOREIGN KEY (`id`) REFERENCES `attachments` (`attach_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci MAX_ROWS=100000 AVG_ROW_LENGTH=1000000; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `attach_data` +-- + +LOCK TABLES `attach_data` WRITE; +/*!40000 ALTER TABLE `attach_data` DISABLE KEYS */; +/*!40000 ALTER TABLE `attach_data` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `attachments` +-- + +DROP TABLE IF EXISTS `attachments`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `attachments` ( + `attach_id` mediumint(9) NOT NULL AUTO_INCREMENT, + `bug_id` mediumint(9) NOT NULL, + `creation_ts` datetime NOT NULL, + `modification_time` datetime NOT NULL, + `description` tinytext NOT NULL, + `mimetype` tinytext NOT NULL, + `ispatch` tinyint(4) NOT NULL DEFAULT 0, + `filename` varchar(255) NOT NULL, + `submitter_id` mediumint(9) NOT NULL, + `isobsolete` tinyint(4) NOT NULL DEFAULT 0, + `isprivate` tinyint(4) NOT NULL DEFAULT 0, + PRIMARY KEY (`attach_id`), + KEY `attachments_bug_id_idx` (`bug_id`), + KEY `attachments_creation_ts_idx` (`creation_ts`), + KEY `attachments_modification_time_idx` (`modification_time`), + KEY `attachments_submitter_id_idx` (`submitter_id`,`bug_id`), + CONSTRAINT `fk_attachments_bug_id_bugs_bug_id` FOREIGN KEY (`bug_id`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_attachments_submitter_id_profiles_userid` FOREIGN KEY (`submitter_id`) REFERENCES `profiles` (`userid`) ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `attachments` +-- + +LOCK TABLES `attachments` WRITE; +/*!40000 ALTER TABLE `attachments` DISABLE KEYS */; +/*!40000 ALTER TABLE `attachments` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `audit_log` +-- + +DROP TABLE IF EXISTS `audit_log`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `audit_log` ( + `user_id` mediumint(9) DEFAULT NULL, + `class` varchar(255) NOT NULL, + `object_id` int(11) NOT NULL, + `field` varchar(64) NOT NULL, + `removed` mediumtext DEFAULT NULL, + `added` mediumtext DEFAULT NULL, + `at_time` datetime NOT NULL, + KEY `audit_log_class_idx` (`class`,`at_time`), + KEY `fk_audit_log_user_id_profiles_userid` (`user_id`), + CONSTRAINT `fk_audit_log_user_id_profiles_userid` FOREIGN KEY (`user_id`) REFERENCES `profiles` (`userid`) ON DELETE SET NULL ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `audit_log` +-- + +LOCK TABLES `audit_log` WRITE; +/*!40000 ALTER TABLE `audit_log` DISABLE KEYS */; +INSERT INTO `audit_log` VALUES (NULL,'Bugzilla::Field',1,'__create__',NULL,'bug_id','2023-09-20 13:12:34'),(NULL,'Bugzilla::Field',2,'__create__',NULL,'short_desc','2023-09-20 13:12:34'),(NULL,'Bugzilla::Field',3,'__create__',NULL,'classification','2023-09-20 13:12:34'),(NULL,'Bugzilla::Field',4,'__create__',NULL,'product','2023-09-20 13:12:34'),(NULL,'Bugzilla::Field',5,'__create__',NULL,'version','2023-09-20 13:12:34'),(NULL,'Bugzilla::Field',6,'__create__',NULL,'rep_platform','2023-09-20 13:12:34'),(NULL,'Bugzilla::Field',7,'__create__',NULL,'bug_file_loc','2023-09-20 13:12:34'),(NULL,'Bugzilla::Field',8,'__create__',NULL,'op_sys','2023-09-20 13:12:34'),(NULL,'Bugzilla::Field',9,'__create__',NULL,'bug_status','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',10,'__create__',NULL,'status_whiteboard','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',11,'__create__',NULL,'keywords','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',12,'__create__',NULL,'resolution','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',13,'__create__',NULL,'bug_severity','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',14,'__create__',NULL,'priority','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',15,'__create__',NULL,'component','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',16,'__create__',NULL,'assigned_to','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',17,'__create__',NULL,'reporter','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',18,'__create__',NULL,'qa_contact','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',19,'__create__',NULL,'assigned_to_realname','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',20,'__create__',NULL,'reporter_realname','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',21,'__create__',NULL,'qa_contact_realname','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',22,'__create__',NULL,'cc','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',23,'__create__',NULL,'dependson','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',24,'__create__',NULL,'blocked','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',25,'__create__',NULL,'attachments.description','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',26,'__create__',NULL,'attachments.filename','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',27,'__create__',NULL,'attachments.mimetype','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',28,'__create__',NULL,'attachments.ispatch','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',29,'__create__',NULL,'attachments.isobsolete','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',30,'__create__',NULL,'attachments.isprivate','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',31,'__create__',NULL,'attachments.submitter','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',32,'__create__',NULL,'target_milestone','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',33,'__create__',NULL,'creation_ts','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',34,'__create__',NULL,'delta_ts','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',35,'__create__',NULL,'longdesc','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',36,'__create__',NULL,'longdescs.isprivate','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',37,'__create__',NULL,'longdescs.count','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',38,'__create__',NULL,'alias','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',39,'__create__',NULL,'everconfirmed','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',40,'__create__',NULL,'reporter_accessible','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',41,'__create__',NULL,'cclist_accessible','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',42,'__create__',NULL,'bug_group','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',43,'__create__',NULL,'estimated_time','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',44,'__create__',NULL,'remaining_time','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',45,'__create__',NULL,'deadline','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',46,'__create__',NULL,'commenter','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',47,'__create__',NULL,'flagtypes.name','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',48,'__create__',NULL,'requestees.login_name','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',49,'__create__',NULL,'setters.login_name','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',50,'__create__',NULL,'work_time','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',51,'__create__',NULL,'percentage_complete','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',52,'__create__',NULL,'content','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',53,'__create__',NULL,'attach_data.thedata','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',54,'__create__',NULL,'owner_idle_time','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',55,'__create__',NULL,'see_also','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',56,'__create__',NULL,'tag','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',57,'__create__',NULL,'last_visit_ts','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',58,'__create__',NULL,'comment_tag','2023-09-20 13:12:35'),(NULL,'Bugzilla::Field',59,'__create__',NULL,'days_elapsed','2023-09-20 13:12:35'),(NULL,'Bugzilla::Classification',1,'__create__',NULL,'Unclassified','2023-09-20 13:12:35'),(NULL,'Bugzilla::Group',1,'__create__',NULL,'admin','2023-09-20 13:12:40'),(NULL,'Bugzilla::Group',2,'__create__',NULL,'tweakparams','2023-09-20 13:12:40'),(NULL,'Bugzilla::Group',3,'__create__',NULL,'editusers','2023-09-20 13:12:40'),(NULL,'Bugzilla::Group',4,'__create__',NULL,'creategroups','2023-09-20 13:12:40'),(NULL,'Bugzilla::Group',5,'__create__',NULL,'editclassifications','2023-09-20 13:12:40'),(NULL,'Bugzilla::Group',6,'__create__',NULL,'editcomponents','2023-09-20 13:12:40'),(NULL,'Bugzilla::Group',7,'__create__',NULL,'editkeywords','2023-09-20 13:12:40'),(NULL,'Bugzilla::Group',8,'__create__',NULL,'editbugs','2023-09-20 13:12:40'),(NULL,'Bugzilla::Group',9,'__create__',NULL,'canconfirm','2023-09-20 13:12:40'),(NULL,'Bugzilla::Group',10,'__create__',NULL,'bz_canusewhineatothers','2023-09-20 13:12:40'),(NULL,'Bugzilla::Group',11,'__create__',NULL,'bz_canusewhines','2023-09-20 13:12:40'),(NULL,'Bugzilla::Group',12,'__create__',NULL,'bz_sudoers','2023-09-20 13:12:40'),(NULL,'Bugzilla::Group',13,'__create__',NULL,'bz_sudo_protect','2023-09-20 13:12:40'),(NULL,'Bugzilla::Group',14,'__create__',NULL,'bz_quip_moderators','2023-09-20 13:12:40'),(NULL,'Bugzilla::User',1,'__create__',NULL,'andreas@hasenkopf.xyz','2023-09-20 13:12:55'),(NULL,'Bugzilla::Product',1,'__create__',NULL,'TestProduct','2023-09-20 13:12:55'),(NULL,'Bugzilla::Version',1,'__create__',NULL,'unspecified','2023-09-20 13:12:55'),(NULL,'Bugzilla::Milestone',1,'__create__',NULL,'---','2023-09-20 13:12:55'),(NULL,'Bugzilla::Component',1,'__create__',NULL,'TestComponent','2023-09-20 13:12:55'),(1,'Bugzilla::Product',2,'__create__',NULL,'Red Hat Enterprise Linux 9','2023-11-27 12:25:54'),(1,'Bugzilla::Version',2,'__create__',NULL,'unspecified','2023-11-27 12:25:54'),(1,'Bugzilla::Milestone',2,'__create__',NULL,'---','2023-11-27 12:25:54'),(1,'Bugzilla::Component',2,'__create__',NULL,'python-bugzilla','2023-11-27 12:25:54'),(1,'Bugzilla::Version',3,'__create__',NULL,'9.0','2023-11-27 12:26:06'),(1,'Bugzilla::Version',4,'__create__',NULL,'9.1','2023-11-27 12:26:14'),(1,'Bugzilla::Product',3,'__create__',NULL,'SUSE Linux Enterprise Server 15 SP6','2023-11-27 12:29:18'),(1,'Bugzilla::Version',5,'__create__',NULL,'unspecified','2023-11-27 12:29:18'),(1,'Bugzilla::Milestone',3,'__create__',NULL,'---','2023-11-27 12:29:18'),(1,'Bugzilla::Component',3,'__create__',NULL,'Kernel','2023-11-27 12:29:18'),(1,'Bugzilla::Component',4,'__create__',NULL,'Containers','2023-11-27 12:29:46'),(1,'Bugzilla::Keyword',1,'__create__',NULL,'FooBar','2024-10-15 13:05:27'),(1,'Bugzilla::Keyword',2,'__create__',NULL,'LoremIpsum','2024-10-15 13:05:52'),(1,'Bugzilla::FlagType',1,'__create__',NULL,'needinfo','2024-10-15 13:26:28'),(1,'Bugzilla::User',2,'__create__',NULL,'nemo@example.com','2024-10-15 13:28:58'); +/*!40000 ALTER TABLE `audit_log` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `bug_group_map` +-- + +DROP TABLE IF EXISTS `bug_group_map`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `bug_group_map` ( + `bug_id` mediumint(9) NOT NULL, + `group_id` mediumint(9) NOT NULL, + UNIQUE KEY `bug_group_map_bug_id_idx` (`bug_id`,`group_id`), + KEY `bug_group_map_group_id_idx` (`group_id`), + CONSTRAINT `fk_bug_group_map_bug_id_bugs_bug_id` FOREIGN KEY (`bug_id`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_bug_group_map_group_id_groups_id` FOREIGN KEY (`group_id`) REFERENCES `groups` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `bug_group_map` +-- + +LOCK TABLES `bug_group_map` WRITE; +/*!40000 ALTER TABLE `bug_group_map` DISABLE KEYS */; +/*!40000 ALTER TABLE `bug_group_map` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `bug_see_also` +-- + +DROP TABLE IF EXISTS `bug_see_also`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `bug_see_also` ( + `id` mediumint(9) NOT NULL AUTO_INCREMENT, + `bug_id` mediumint(9) NOT NULL, + `value` varchar(255) NOT NULL, + `class` varchar(255) NOT NULL DEFAULT '', + PRIMARY KEY (`id`), + UNIQUE KEY `bug_see_also_bug_id_idx` (`bug_id`,`value`), + CONSTRAINT `fk_bug_see_also_bug_id_bugs_bug_id` FOREIGN KEY (`bug_id`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `bug_see_also` +-- + +LOCK TABLES `bug_see_also` WRITE; +/*!40000 ALTER TABLE `bug_see_also` DISABLE KEYS */; +/*!40000 ALTER TABLE `bug_see_also` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `bug_severity` +-- + +DROP TABLE IF EXISTS `bug_severity`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `bug_severity` ( + `id` smallint(6) NOT NULL AUTO_INCREMENT, + `value` varchar(64) NOT NULL, + `sortkey` smallint(6) NOT NULL DEFAULT 0, + `isactive` tinyint(4) NOT NULL DEFAULT 1, + `visibility_value_id` smallint(6) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `bug_severity_value_idx` (`value`), + KEY `bug_severity_sortkey_idx` (`sortkey`,`value`), + KEY `bug_severity_visibility_value_id_idx` (`visibility_value_id`) +) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `bug_severity` +-- + +LOCK TABLES `bug_severity` WRITE; +/*!40000 ALTER TABLE `bug_severity` DISABLE KEYS */; +INSERT INTO `bug_severity` VALUES (1,'blocker',100,1,NULL),(2,'critical',200,1,NULL),(3,'major',300,1,NULL),(4,'normal',400,1,NULL),(5,'minor',500,1,NULL),(6,'trivial',600,1,NULL),(7,'enhancement',700,1,NULL); +/*!40000 ALTER TABLE `bug_severity` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `bug_status` +-- + +DROP TABLE IF EXISTS `bug_status`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `bug_status` ( + `id` smallint(6) NOT NULL AUTO_INCREMENT, + `value` varchar(64) NOT NULL, + `sortkey` smallint(6) NOT NULL DEFAULT 0, + `isactive` tinyint(4) NOT NULL DEFAULT 1, + `visibility_value_id` smallint(6) DEFAULT NULL, + `is_open` tinyint(4) NOT NULL DEFAULT 1, + PRIMARY KEY (`id`), + UNIQUE KEY `bug_status_value_idx` (`value`), + KEY `bug_status_sortkey_idx` (`sortkey`,`value`), + KEY `bug_status_visibility_value_id_idx` (`visibility_value_id`) +) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `bug_status` +-- + +LOCK TABLES `bug_status` WRITE; +/*!40000 ALTER TABLE `bug_status` DISABLE KEYS */; +INSERT INTO `bug_status` VALUES (1,'UNCONFIRMED',100,1,NULL,1),(2,'CONFIRMED',200,1,NULL,1),(3,'IN_PROGRESS',300,1,NULL,1),(4,'RESOLVED',400,1,NULL,0),(5,'VERIFIED',500,1,NULL,0); +/*!40000 ALTER TABLE `bug_status` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `bug_tag` +-- + +DROP TABLE IF EXISTS `bug_tag`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `bug_tag` ( + `bug_id` mediumint(9) NOT NULL, + `tag_id` mediumint(9) NOT NULL, + UNIQUE KEY `bug_tag_bug_id_idx` (`bug_id`,`tag_id`), + KEY `fk_bug_tag_tag_id_tag_id` (`tag_id`), + CONSTRAINT `fk_bug_tag_bug_id_bugs_bug_id` FOREIGN KEY (`bug_id`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_bug_tag_tag_id_tag_id` FOREIGN KEY (`tag_id`) REFERENCES `tag` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `bug_tag` +-- + +LOCK TABLES `bug_tag` WRITE; +/*!40000 ALTER TABLE `bug_tag` DISABLE KEYS */; +/*!40000 ALTER TABLE `bug_tag` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `bug_user_last_visit` +-- + +DROP TABLE IF EXISTS `bug_user_last_visit`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `bug_user_last_visit` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `user_id` mediumint(9) NOT NULL, + `bug_id` mediumint(9) NOT NULL, + `last_visit_ts` datetime NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `bug_user_last_visit_idx` (`user_id`,`bug_id`), + KEY `bug_user_last_visit_last_visit_ts_idx` (`last_visit_ts`), + KEY `fk_bug_user_last_visit_bug_id_bugs_bug_id` (`bug_id`), + CONSTRAINT `fk_bug_user_last_visit_bug_id_bugs_bug_id` FOREIGN KEY (`bug_id`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_bug_user_last_visit_user_id_profiles_userid` FOREIGN KEY (`user_id`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `bug_user_last_visit` +-- + +LOCK TABLES `bug_user_last_visit` WRITE; +/*!40000 ALTER TABLE `bug_user_last_visit` DISABLE KEYS */; +INSERT INTO `bug_user_last_visit` VALUES (1,1,1,'2024-10-15 14:00:54'),(2,1,2,'2024-10-15 14:00:49'),(3,1,3,'2024-10-15 13:45:42'); +/*!40000 ALTER TABLE `bug_user_last_visit` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `bugs` +-- + +DROP TABLE IF EXISTS `bugs`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `bugs` ( + `bug_id` mediumint(9) NOT NULL AUTO_INCREMENT, + `assigned_to` mediumint(9) NOT NULL, + `bug_file_loc` mediumtext NOT NULL DEFAULT '', + `bug_severity` varchar(64) NOT NULL, + `bug_status` varchar(64) NOT NULL, + `creation_ts` datetime DEFAULT NULL, + `delta_ts` datetime NOT NULL, + `short_desc` varchar(255) NOT NULL, + `op_sys` varchar(64) NOT NULL, + `priority` varchar(64) NOT NULL, + `product_id` smallint(6) NOT NULL, + `rep_platform` varchar(64) NOT NULL, + `reporter` mediumint(9) NOT NULL, + `version` varchar(64) NOT NULL, + `component_id` mediumint(9) NOT NULL, + `resolution` varchar(64) NOT NULL DEFAULT '', + `target_milestone` varchar(64) NOT NULL DEFAULT '---', + `qa_contact` mediumint(9) DEFAULT NULL, + `status_whiteboard` mediumtext NOT NULL DEFAULT '', + `lastdiffed` datetime DEFAULT NULL, + `everconfirmed` tinyint(4) NOT NULL, + `reporter_accessible` tinyint(4) NOT NULL DEFAULT 1, + `cclist_accessible` tinyint(4) NOT NULL DEFAULT 1, + `estimated_time` decimal(7,2) NOT NULL DEFAULT 0.00, + `remaining_time` decimal(7,2) NOT NULL DEFAULT 0.00, + `deadline` datetime DEFAULT NULL, + PRIMARY KEY (`bug_id`), + KEY `bugs_assigned_to_idx` (`assigned_to`), + KEY `bugs_creation_ts_idx` (`creation_ts`), + KEY `bugs_delta_ts_idx` (`delta_ts`), + KEY `bugs_bug_severity_idx` (`bug_severity`), + KEY `bugs_bug_status_idx` (`bug_status`), + KEY `bugs_op_sys_idx` (`op_sys`), + KEY `bugs_priority_idx` (`priority`), + KEY `bugs_product_id_idx` (`product_id`), + KEY `bugs_reporter_idx` (`reporter`), + KEY `bugs_version_idx` (`version`), + KEY `bugs_component_id_idx` (`component_id`), + KEY `bugs_resolution_idx` (`resolution`), + KEY `bugs_target_milestone_idx` (`target_milestone`), + KEY `bugs_qa_contact_idx` (`qa_contact`), + CONSTRAINT `fk_bugs_assigned_to_profiles_userid` FOREIGN KEY (`assigned_to`) REFERENCES `profiles` (`userid`) ON UPDATE CASCADE, + CONSTRAINT `fk_bugs_component_id_components_id` FOREIGN KEY (`component_id`) REFERENCES `components` (`id`) ON UPDATE CASCADE, + CONSTRAINT `fk_bugs_product_id_products_id` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON UPDATE CASCADE, + CONSTRAINT `fk_bugs_qa_contact_profiles_userid` FOREIGN KEY (`qa_contact`) REFERENCES `profiles` (`userid`) ON UPDATE CASCADE, + CONSTRAINT `fk_bugs_reporter_profiles_userid` FOREIGN KEY (`reporter`) REFERENCES `profiles` (`userid`) ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `bugs` +-- + +LOCK TABLES `bugs` WRITE; +/*!40000 ALTER TABLE `bugs` DISABLE KEYS */; +INSERT INTO `bugs` VALUES (1,1,'','major','IN_PROGRESS','2023-11-27 15:35:33','2023-11-27 15:53:04','ZeroDivisionError in function foo_bar()','Linux','---',3,'PC',1,'unspecified',4,'','---',NULL,'AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:L','2023-11-27 15:53:04',1,1,1,0.00,0.00,NULL),(2,1,'','enhancement','CONFIRMED','2023-11-27 15:38:45','2024-10-15 13:29:13','Expect the Spanish inquisition','Linux','---',2,'PC',1,'9.1',2,'','---',NULL,'lorem ipsum','2024-10-15 13:29:13',1,1,1,0.00,0.00,NULL),(3,1,'','enhancement','CONFIRMED','2024-10-15 13:45:40','2024-10-15 13:45:40','Kernel Panic in the Discothek','Linux','---',3,'PC',1,'unspecified',3,'','---',NULL,'','2024-10-15 13:45:40',1,1,1,0.00,0.00,NULL); +/*!40000 ALTER TABLE `bugs` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `bugs_activity` +-- + +DROP TABLE IF EXISTS `bugs_activity`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `bugs_activity` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `bug_id` mediumint(9) NOT NULL, + `attach_id` mediumint(9) DEFAULT NULL, + `who` mediumint(9) NOT NULL, + `bug_when` datetime NOT NULL, + `fieldid` mediumint(9) NOT NULL, + `added` varchar(255) DEFAULT NULL, + `removed` varchar(255) DEFAULT NULL, + `comment_id` int(11) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `bugs_activity_bug_id_idx` (`bug_id`), + KEY `bugs_activity_who_idx` (`who`), + KEY `bugs_activity_bug_when_idx` (`bug_when`), + KEY `bugs_activity_fieldid_idx` (`fieldid`), + KEY `bugs_activity_added_idx` (`added`), + KEY `bugs_activity_removed_idx` (`removed`), + KEY `fk_bugs_activity_attach_id_attachments_attach_id` (`attach_id`), + KEY `fk_bugs_activity_comment_id_longdescs_comment_id` (`comment_id`), + CONSTRAINT `fk_bugs_activity_attach_id_attachments_attach_id` FOREIGN KEY (`attach_id`) REFERENCES `attachments` (`attach_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_bugs_activity_bug_id_bugs_bug_id` FOREIGN KEY (`bug_id`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_bugs_activity_comment_id_longdescs_comment_id` FOREIGN KEY (`comment_id`) REFERENCES `longdescs` (`comment_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_bugs_activity_fieldid_fielddefs_id` FOREIGN KEY (`fieldid`) REFERENCES `fielddefs` (`id`) ON UPDATE CASCADE, + CONSTRAINT `fk_bugs_activity_who_profiles_userid` FOREIGN KEY (`who`) REFERENCES `profiles` (`userid`) ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `bugs_activity` +-- + +LOCK TABLES `bugs_activity` WRITE; +/*!40000 ALTER TABLE `bugs_activity` DISABLE KEYS */; +INSERT INTO `bugs_activity` VALUES (1,1,NULL,1,'2023-11-27 15:45:09',9,'IN_PROGRESS','CONFIRMED',NULL),(2,1,NULL,1,'2023-11-27 15:47:58',10,'AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:L','',NULL),(3,1,NULL,1,'2023-11-27 15:53:04',38,'FOO-1','',NULL),(4,2,NULL,1,'2024-10-15 13:08:14',10,'lorem ipsum','',NULL),(5,2,NULL,1,'2024-10-15 13:08:14',11,'FooBar','',NULL),(6,2,NULL,1,'2024-10-15 13:29:13',47,'needinfo?(nemo@example.com)','',NULL),(7,2,NULL,1,'2024-10-15 13:29:13',22,'nemo@example.com','',NULL); +/*!40000 ALTER TABLE `bugs_activity` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `bugs_aliases` +-- + +DROP TABLE IF EXISTS `bugs_aliases`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `bugs_aliases` ( + `alias` varchar(40) NOT NULL, + `bug_id` mediumint(9) DEFAULT NULL, + UNIQUE KEY `bugs_aliases_alias_idx` (`alias`), + KEY `bugs_aliases_bug_id_idx` (`bug_id`), + CONSTRAINT `fk_bugs_aliases_bug_id_bugs_bug_id` FOREIGN KEY (`bug_id`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `bugs_aliases` +-- + +LOCK TABLES `bugs_aliases` WRITE; +/*!40000 ALTER TABLE `bugs_aliases` DISABLE KEYS */; +INSERT INTO `bugs_aliases` VALUES ('FOO-1',1); +/*!40000 ALTER TABLE `bugs_aliases` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `bugs_fulltext` +-- + +DROP TABLE IF EXISTS `bugs_fulltext`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `bugs_fulltext` ( + `bug_id` mediumint(9) NOT NULL, + `short_desc` varchar(255) NOT NULL, + `comments` mediumtext DEFAULT NULL, + `comments_noprivate` mediumtext DEFAULT NULL, + PRIMARY KEY (`bug_id`), + FULLTEXT KEY `bugs_fulltext_short_desc_idx` (`short_desc`), + FULLTEXT KEY `bugs_fulltext_comments_idx` (`comments`), + FULLTEXT KEY `bugs_fulltext_comments_noprivate_idx` (`comments_noprivate`), + CONSTRAINT `fk_bugs_fulltext_bug_id_bugs_bug_id` FOREIGN KEY (`bug_id`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `bugs_fulltext` +-- + +LOCK TABLES `bugs_fulltext` WRITE; +/*!40000 ALTER TABLE `bugs_fulltext` DISABLE KEYS */; +INSERT INTO `bugs_fulltext` VALUES (1,'ZeroDivisionError in function foo_bar()','Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\n\nAt vero eos et accusam et justo duo dolores et ea rebum.\nStet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.','Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\n\nAt vero eos et accusam et justo duo dolores et ea rebum.\nStet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.'),(2,'Expect the Spanish inquisition','Nobody expects the Spanish Inquisition! \n\nOur chief weapon is surprise, surprise and fear, fear and surprise. \n\nOur two weapons are fear and surprise, and ruthless efficiency. \n\nOur three weapons are fear and surprise and ruthless efficiency and an almost fanatical dedication to the pope.','Nobody expects the Spanish Inquisition! \n\nOur chief weapon is surprise, surprise and fear, fear and surprise. \n\nOur two weapons are fear and surprise, and ruthless efficiency. \n\nOur three weapons are fear and surprise and ruthless efficiency and an almost fanatical dedication to the pope.'),(3,'Kernel Panic in the Discothek','lorem ipsum dolor sit amet','lorem ipsum dolor sit amet'); +/*!40000 ALTER TABLE `bugs_fulltext` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `bz_schema` +-- + +DROP TABLE IF EXISTS `bz_schema`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `bz_schema` ( + `schema_data` longblob NOT NULL, + `version` decimal(3,2) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `bz_schema` +-- + +LOCK TABLES `bz_schema` WRITE; +/*!40000 ALTER TABLE `bz_schema` DISABLE KEYS */; +INSERT INTO `bz_schema` VALUES ('$VAR1 = {\n \'attach_data\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'attach_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'attachments\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'thedata\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'LONGBLOB\'\n }\n ]\n },\n \'attachments\' => {\n \'FIELDS\' => [\n \'attach_id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'bug_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'creation_ts\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'DATETIME\'\n },\n \'modification_time\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'DATETIME\'\n },\n \'description\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'TINYTEXT\'\n },\n \'mimetype\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'TINYTEXT\'\n },\n \'ispatch\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'filename\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(255)\'\n },\n \'submitter_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'isobsolete\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'isprivate\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n }\n ],\n \'INDEXES\' => [\n \'attachments_bug_id_idx\',\n [\n \'bug_id\'\n ],\n \'attachments_creation_ts_idx\',\n [\n \'creation_ts\'\n ],\n \'attachments_modification_time_idx\',\n [\n \'modification_time\'\n ],\n \'attachments_submitter_id_idx\',\n [\n \'submitter_id\',\n \'bug_id\'\n ]\n ]\n },\n \'audit_log\' => {\n \'FIELDS\' => [\n \'user_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'SET NULL\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'class\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(255)\'\n },\n \'object_id\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT4\'\n },\n \'field\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'removed\',\n {\n \'TYPE\' => \'MEDIUMTEXT\'\n },\n \'added\',\n {\n \'TYPE\' => \'MEDIUMTEXT\'\n },\n \'at_time\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'DATETIME\'\n }\n ],\n \'INDEXES\' => [\n \'audit_log_class_idx\',\n [\n \'class\',\n \'at_time\'\n ]\n ]\n },\n \'bug_group_map\' => {\n \'FIELDS\' => [\n \'bug_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'group_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'groups\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'bug_group_map_bug_id_idx\',\n {\n \'FIELDS\' => [\n \'bug_id\',\n \'group_id\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'bug_group_map_group_id_idx\',\n [\n \'group_id\'\n ]\n ]\n },\n \'bug_see_also\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'bug_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'value\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(255)\'\n },\n \'class\',\n {\n \'DEFAULT\' => \'\\\'\\\'\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(255)\'\n }\n ],\n \'INDEXES\' => [\n \'bug_see_also_bug_id_idx\',\n {\n \'FIELDS\' => [\n \'bug_id\',\n \'value\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'bug_severity\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'SMALLSERIAL\'\n },\n \'value\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'sortkey\',\n {\n \'DEFAULT\' => 0,\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n },\n \'isactive\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'visibility_value_id\',\n {\n \'TYPE\' => \'INT2\'\n }\n ],\n \'INDEXES\' => [\n \'bug_severity_value_idx\',\n {\n \'FIELDS\' => [\n \'value\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'bug_severity_sortkey_idx\',\n [\n \'sortkey\',\n \'value\'\n ],\n \'bug_severity_visibility_value_id_idx\',\n [\n \'visibility_value_id\'\n ]\n ]\n },\n \'bug_status\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'SMALLSERIAL\'\n },\n \'value\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'sortkey\',\n {\n \'DEFAULT\' => 0,\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n },\n \'isactive\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'visibility_value_id\',\n {\n \'TYPE\' => \'INT2\'\n },\n \'is_open\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n }\n ],\n \'INDEXES\' => [\n \'bug_status_value_idx\',\n {\n \'FIELDS\' => [\n \'value\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'bug_status_sortkey_idx\',\n [\n \'sortkey\',\n \'value\'\n ],\n \'bug_status_visibility_value_id_idx\',\n [\n \'visibility_value_id\'\n ]\n ]\n },\n \'bug_tag\' => {\n \'FIELDS\' => [\n \'bug_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'tag_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'tag\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'bug_tag_bug_id_idx\',\n {\n \'FIELDS\' => [\n \'bug_id\',\n \'tag_id\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'bug_user_last_visit\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'INTSERIAL\'\n },\n \'user_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'bug_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'last_visit_ts\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'DATETIME\'\n }\n ],\n \'INDEXES\' => [\n \'bug_user_last_visit_idx\',\n {\n \'FIELDS\' => [\n \'user_id\',\n \'bug_id\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'bug_user_last_visit_last_visit_ts_idx\',\n [\n \'last_visit_ts\'\n ]\n ]\n },\n \'bugs\' => {\n \'FIELDS\' => [\n \'bug_id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'assigned_to\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'bug_file_loc\',\n {\n \'DEFAULT\' => \'\\\'\\\'\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'MEDIUMTEXT\'\n },\n \'bug_severity\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'bug_status\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'creation_ts\',\n {\n \'TYPE\' => \'DATETIME\'\n },\n \'delta_ts\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'DATETIME\'\n },\n \'short_desc\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(255)\'\n },\n \'op_sys\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'priority\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'product_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'TABLE\' => \'products\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT2\'\n },\n \'rep_platform\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'reporter\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'version\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'component_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'TABLE\' => \'components\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'resolution\',\n {\n \'DEFAULT\' => \'\\\'\\\'\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'target_milestone\',\n {\n \'DEFAULT\' => \'\\\'---\\\'\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'qa_contact\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'status_whiteboard\',\n {\n \'DEFAULT\' => \'\\\'\\\'\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'MEDIUMTEXT\'\n },\n \'lastdiffed\',\n {\n \'TYPE\' => \'DATETIME\'\n },\n \'everconfirmed\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'reporter_accessible\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'cclist_accessible\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'estimated_time\',\n {\n \'DEFAULT\' => \'0\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'decimal(7,2)\'\n },\n \'remaining_time\',\n {\n \'DEFAULT\' => \'0\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'decimal(7,2)\'\n },\n \'deadline\',\n {\n \'TYPE\' => \'DATETIME\'\n }\n ],\n \'INDEXES\' => [\n \'bugs_assigned_to_idx\',\n [\n \'assigned_to\'\n ],\n \'bugs_creation_ts_idx\',\n [\n \'creation_ts\'\n ],\n \'bugs_delta_ts_idx\',\n [\n \'delta_ts\'\n ],\n \'bugs_bug_severity_idx\',\n [\n \'bug_severity\'\n ],\n \'bugs_bug_status_idx\',\n [\n \'bug_status\'\n ],\n \'bugs_op_sys_idx\',\n [\n \'op_sys\'\n ],\n \'bugs_priority_idx\',\n [\n \'priority\'\n ],\n \'bugs_product_id_idx\',\n [\n \'product_id\'\n ],\n \'bugs_reporter_idx\',\n [\n \'reporter\'\n ],\n \'bugs_version_idx\',\n [\n \'version\'\n ],\n \'bugs_component_id_idx\',\n [\n \'component_id\'\n ],\n \'bugs_resolution_idx\',\n [\n \'resolution\'\n ],\n \'bugs_target_milestone_idx\',\n [\n \'target_milestone\'\n ],\n \'bugs_qa_contact_idx\',\n [\n \'qa_contact\'\n ]\n ]\n },\n \'bugs_activity\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'INTSERIAL\'\n },\n \'bug_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'attach_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'attach_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'attachments\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'who\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'bug_when\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'DATETIME\'\n },\n \'fieldid\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'TABLE\' => \'fielddefs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'added\',\n {\n \'TYPE\' => \'varchar(255)\'\n },\n \'removed\',\n {\n \'TYPE\' => \'varchar(255)\'\n },\n \'comment_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'comment_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'longdescs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT4\'\n }\n ],\n \'INDEXES\' => [\n \'bugs_activity_bug_id_idx\',\n [\n \'bug_id\'\n ],\n \'bugs_activity_who_idx\',\n [\n \'who\'\n ],\n \'bugs_activity_bug_when_idx\',\n [\n \'bug_when\'\n ],\n \'bugs_activity_fieldid_idx\',\n [\n \'fieldid\'\n ],\n \'bugs_activity_added_idx\',\n [\n \'added\'\n ],\n \'bugs_activity_removed_idx\',\n [\n \'removed\'\n ]\n ]\n },\n \'bugs_aliases\' => {\n \'FIELDS\' => [\n \'alias\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(40)\'\n },\n \'bug_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'bugs_aliases_bug_id_idx\',\n [\n \'bug_id\'\n ],\n \'bugs_aliases_alias_idx\',\n {\n \'FIELDS\' => [\n \'alias\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'bugs_fulltext\' => {\n \'FIELDS\' => [\n \'bug_id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'short_desc\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(255)\'\n },\n \'comments\',\n {\n \'TYPE\' => \'LONGTEXT\'\n },\n \'comments_noprivate\',\n {\n \'TYPE\' => \'LONGTEXT\'\n }\n ],\n \'INDEXES\' => [\n \'bugs_fulltext_short_desc_idx\',\n {\n \'FIELDS\' => [\n \'short_desc\'\n ],\n \'TYPE\' => \'FULLTEXT\'\n },\n \'bugs_fulltext_comments_idx\',\n {\n \'FIELDS\' => [\n \'comments\'\n ],\n \'TYPE\' => \'FULLTEXT\'\n },\n \'bugs_fulltext_comments_noprivate_idx\',\n {\n \'FIELDS\' => [\n \'comments_noprivate\'\n ],\n \'TYPE\' => \'FULLTEXT\'\n }\n ]\n },\n \'bz_schema\' => {\n \'FIELDS\' => [\n \'schema_data\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'LONGBLOB\'\n },\n \'version\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'decimal(3,2)\'\n }\n ]\n },\n \'category_group_map\' => {\n \'FIELDS\' => [\n \'category_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'series_categories\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT2\'\n },\n \'group_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'groups\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'category_group_map_category_id_idx\',\n {\n \'FIELDS\' => [\n \'category_id\',\n \'group_id\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'cc\' => {\n \'FIELDS\' => [\n \'bug_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'who\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'cc_bug_id_idx\',\n {\n \'FIELDS\' => [\n \'bug_id\',\n \'who\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'cc_who_idx\',\n [\n \'who\'\n ]\n ]\n },\n \'classifications\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'SMALLSERIAL\'\n },\n \'name\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'description\',\n {\n \'TYPE\' => \'MEDIUMTEXT\'\n },\n \'sortkey\',\n {\n \'DEFAULT\' => \'0\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n }\n ],\n \'INDEXES\' => [\n \'classifications_name_idx\',\n {\n \'FIELDS\' => [\n \'name\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'component_cc\' => {\n \'FIELDS\' => [\n \'user_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'component_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'components\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'component_cc_user_id_idx\',\n {\n \'FIELDS\' => [\n \'component_id\',\n \'user_id\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'components\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'name\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'product_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'products\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT2\'\n },\n \'initialowner\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'initialqacontact\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'SET NULL\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'description\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'MEDIUMTEXT\'\n },\n \'isactive\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n }\n ],\n \'INDEXES\' => [\n \'components_product_id_idx\',\n {\n \'FIELDS\' => [\n \'product_id\',\n \'name\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'components_name_idx\',\n [\n \'name\'\n ]\n ]\n },\n \'dependencies\' => {\n \'FIELDS\' => [\n \'blocked\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'dependson\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'dependencies_blocked_idx\',\n {\n \'FIELDS\' => [\n \'blocked\',\n \'dependson\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'dependencies_dependson_idx\',\n [\n \'dependson\'\n ]\n ]\n },\n \'duplicates\' => {\n \'FIELDS\' => [\n \'dupe_of\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'dupe\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ]\n },\n \'email_bug_ignore\' => {\n \'FIELDS\' => [\n \'user_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'bug_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'email_bug_ignore_user_id_idx\',\n {\n \'FIELDS\' => [\n \'user_id\',\n \'bug_id\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'email_setting\' => {\n \'FIELDS\' => [\n \'user_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'relationship\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT1\'\n },\n \'event\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT1\'\n }\n ],\n \'INDEXES\' => [\n \'email_setting_user_id_idx\',\n {\n \'FIELDS\' => [\n \'user_id\',\n \'relationship\',\n \'event\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'field_visibility\' => {\n \'FIELDS\' => [\n \'field_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'fielddefs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'value_id\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n }\n ],\n \'INDEXES\' => [\n \'field_visibility_field_id_idx\',\n {\n \'FIELDS\' => [\n \'field_id\',\n \'value_id\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'fielddefs\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'name\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'type\',\n {\n \'DEFAULT\' => 0,\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n },\n \'custom\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'description\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'TINYTEXT\'\n },\n \'long_desc\',\n {\n \'DEFAULT\' => \'\\\'\\\'\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(255)\'\n },\n \'mailhead\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'sortkey\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n },\n \'obsolete\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'enter_bug\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'buglist\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'visibility_field_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'TABLE\' => \'fielddefs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'value_field_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'TABLE\' => \'fielddefs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'reverse_desc\',\n {\n \'TYPE\' => \'TINYTEXT\'\n },\n \'is_mandatory\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'is_numeric\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n }\n ],\n \'INDEXES\' => [\n \'fielddefs_name_idx\',\n {\n \'FIELDS\' => [\n \'name\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'fielddefs_sortkey_idx\',\n [\n \'sortkey\'\n ],\n \'fielddefs_value_field_id_idx\',\n [\n \'value_field_id\'\n ],\n \'fielddefs_is_mandatory_idx\',\n [\n \'is_mandatory\'\n ]\n ]\n },\n \'flagexclusions\' => {\n \'FIELDS\' => [\n \'type_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'flagtypes\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'product_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'products\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT2\'\n },\n \'component_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'components\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'flagexclusions_type_id_idx\',\n {\n \'FIELDS\' => [\n \'type_id\',\n \'product_id\',\n \'component_id\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'flaginclusions\' => {\n \'FIELDS\' => [\n \'type_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'flagtypes\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'product_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'products\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT2\'\n },\n \'component_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'components\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'flaginclusions_type_id_idx\',\n {\n \'FIELDS\' => [\n \'type_id\',\n \'product_id\',\n \'component_id\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'flags\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'type_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'flagtypes\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'status\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'char(1)\'\n },\n \'bug_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'attach_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'attach_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'attachments\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'creation_date\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'DATETIME\'\n },\n \'modification_date\',\n {\n \'TYPE\' => \'DATETIME\'\n },\n \'setter_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'requestee_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'flags_bug_id_idx\',\n [\n \'bug_id\',\n \'attach_id\'\n ],\n \'flags_setter_id_idx\',\n [\n \'setter_id\'\n ],\n \'flags_requestee_id_idx\',\n [\n \'requestee_id\'\n ],\n \'flags_type_id_idx\',\n [\n \'type_id\'\n ]\n ]\n },\n \'flagtypes\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'name\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(50)\'\n },\n \'description\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'MEDIUMTEXT\'\n },\n \'cc_list\',\n {\n \'TYPE\' => \'varchar(200)\'\n },\n \'target_type\',\n {\n \'DEFAULT\' => \'\\\'b\\\'\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'char(1)\'\n },\n \'is_active\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'is_requestable\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'is_requesteeble\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'is_multiplicable\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'sortkey\',\n {\n \'DEFAULT\' => \'0\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n },\n \'grant_group_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'SET NULL\',\n \'TABLE\' => \'groups\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'request_group_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'SET NULL\',\n \'TABLE\' => \'groups\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ]\n },\n \'group_control_map\' => {\n \'FIELDS\' => [\n \'group_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'groups\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'product_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'products\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT2\'\n },\n \'entry\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'membercontrol\',\n {\n \'DEFAULT\' => \'0\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT1\'\n },\n \'othercontrol\',\n {\n \'DEFAULT\' => \'0\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT1\'\n },\n \'canedit\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'editcomponents\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'editbugs\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'canconfirm\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n }\n ],\n \'INDEXES\' => [\n \'group_control_map_product_id_idx\',\n {\n \'FIELDS\' => [\n \'product_id\',\n \'group_id\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'group_control_map_group_id_idx\',\n [\n \'group_id\'\n ]\n ]\n },\n \'group_group_map\' => {\n \'FIELDS\' => [\n \'member_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'groups\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'grantor_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'groups\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'grant_type\',\n {\n \'DEFAULT\' => \'0\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT1\'\n }\n ],\n \'INDEXES\' => [\n \'group_group_map_member_id_idx\',\n {\n \'FIELDS\' => [\n \'member_id\',\n \'grantor_id\',\n \'grant_type\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'groups\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'name\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(255)\'\n },\n \'description\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'MEDIUMTEXT\'\n },\n \'isbuggroup\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'userregexp\',\n {\n \'DEFAULT\' => \'\\\'\\\'\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'TINYTEXT\'\n },\n \'isactive\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'icon_url\',\n {\n \'TYPE\' => \'TINYTEXT\'\n }\n ],\n \'INDEXES\' => [\n \'groups_name_idx\',\n {\n \'FIELDS\' => [\n \'name\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'keyworddefs\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'SMALLSERIAL\'\n },\n \'name\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'description\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'MEDIUMTEXT\'\n }\n ],\n \'INDEXES\' => [\n \'keyworddefs_name_idx\',\n {\n \'FIELDS\' => [\n \'name\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'keywords\' => {\n \'FIELDS\' => [\n \'bug_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'keywordid\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'keyworddefs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT2\'\n }\n ],\n \'INDEXES\' => [\n \'keywords_bug_id_idx\',\n {\n \'FIELDS\' => [\n \'bug_id\',\n \'keywordid\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'keywords_keywordid_idx\',\n [\n \'keywordid\'\n ]\n ]\n },\n \'login_failure\' => {\n \'FIELDS\' => [\n \'user_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'login_time\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'DATETIME\'\n },\n \'ip_addr\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(40)\'\n }\n ],\n \'INDEXES\' => [\n \'login_failure_user_id_idx\',\n [\n \'user_id\'\n ]\n ]\n },\n \'logincookies\' => {\n \'FIELDS\' => [\n \'cookie\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'varchar(16)\'\n },\n \'userid\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'ipaddr\',\n {\n \'TYPE\' => \'varchar(40)\'\n },\n \'lastused\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'DATETIME\'\n }\n ],\n \'INDEXES\' => [\n \'logincookies_lastused_idx\',\n [\n \'lastused\'\n ]\n ]\n },\n \'longdescs\' => {\n \'FIELDS\' => [\n \'comment_id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'INTSERIAL\'\n },\n \'bug_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'who\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'bug_when\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'DATETIME\'\n },\n \'work_time\',\n {\n \'DEFAULT\' => \'0\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'decimal(7,2)\'\n },\n \'thetext\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'LONGTEXT\'\n },\n \'isprivate\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'already_wrapped\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'type\',\n {\n \'DEFAULT\' => \'0\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n },\n \'extra_data\',\n {\n \'TYPE\' => \'varchar(255)\'\n }\n ],\n \'INDEXES\' => [\n \'longdescs_bug_id_idx\',\n [\n \'bug_id\',\n \'work_time\'\n ],\n \'longdescs_who_idx\',\n [\n \'who\',\n \'bug_id\'\n ],\n \'longdescs_bug_when_idx\',\n [\n \'bug_when\'\n ]\n ]\n },\n \'longdescs_tags\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'comment_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'comment_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'longdescs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT4\'\n },\n \'tag\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(24)\'\n }\n ],\n \'INDEXES\' => [\n \'longdescs_tags_idx\',\n {\n \'FIELDS\' => [\n \'comment_id\',\n \'tag\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'longdescs_tags_activity\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'bug_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'bug_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bugs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'comment_id\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'comment_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'longdescs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT4\'\n },\n \'who\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'bug_when\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'DATETIME\'\n },\n \'added\',\n {\n \'TYPE\' => \'varchar(24)\'\n },\n \'removed\',\n {\n \'TYPE\' => \'varchar(24)\'\n }\n ],\n \'INDEXES\' => [\n \'longdescs_tags_activity_bug_id_idx\',\n [\n \'bug_id\'\n ]\n ]\n },\n \'longdescs_tags_weights\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'tag\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(24)\'\n },\n \'weight\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'longdescs_tags_weights_tag_idx\',\n {\n \'FIELDS\' => [\n \'tag\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'mail_staging\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'INTSERIAL\'\n },\n \'message\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'LONGBLOB\'\n }\n ]\n },\n \'milestones\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'product_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'products\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT2\'\n },\n \'value\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'sortkey\',\n {\n \'DEFAULT\' => 0,\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n },\n \'isactive\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n }\n ],\n \'INDEXES\' => [\n \'milestones_product_id_idx\',\n {\n \'FIELDS\' => [\n \'product_id\',\n \'value\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'namedqueries\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'userid\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'name\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'query\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'LONGTEXT\'\n }\n ],\n \'INDEXES\' => [\n \'namedqueries_userid_idx\',\n {\n \'FIELDS\' => [\n \'userid\',\n \'name\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'namedqueries_link_in_footer\' => {\n \'FIELDS\' => [\n \'namedquery_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'namedqueries\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'user_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'namedqueries_link_in_footer_id_idx\',\n {\n \'FIELDS\' => [\n \'namedquery_id\',\n \'user_id\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'namedqueries_link_in_footer_userid_idx\',\n [\n \'user_id\'\n ]\n ]\n },\n \'namedquery_group_map\' => {\n \'FIELDS\' => [\n \'namedquery_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'namedqueries\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'group_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'groups\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'namedquery_group_map_namedquery_id_idx\',\n {\n \'FIELDS\' => [\n \'namedquery_id\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'namedquery_group_map_group_id_idx\',\n [\n \'group_id\'\n ]\n ]\n },\n \'op_sys\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'SMALLSERIAL\'\n },\n \'value\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'sortkey\',\n {\n \'DEFAULT\' => 0,\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n },\n \'isactive\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'visibility_value_id\',\n {\n \'TYPE\' => \'INT2\'\n }\n ],\n \'INDEXES\' => [\n \'op_sys_value_idx\',\n {\n \'FIELDS\' => [\n \'value\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'op_sys_sortkey_idx\',\n [\n \'sortkey\',\n \'value\'\n ],\n \'op_sys_visibility_value_id_idx\',\n [\n \'visibility_value_id\'\n ]\n ]\n },\n \'priority\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'SMALLSERIAL\'\n },\n \'value\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'sortkey\',\n {\n \'DEFAULT\' => 0,\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n },\n \'isactive\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'visibility_value_id\',\n {\n \'TYPE\' => \'INT2\'\n }\n ],\n \'INDEXES\' => [\n \'priority_value_idx\',\n {\n \'FIELDS\' => [\n \'value\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'priority_sortkey_idx\',\n [\n \'sortkey\',\n \'value\'\n ],\n \'priority_visibility_value_id_idx\',\n [\n \'visibility_value_id\'\n ]\n ]\n },\n \'products\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'SMALLSERIAL\'\n },\n \'name\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'classification_id\',\n {\n \'DEFAULT\' => \'1\',\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'classifications\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT2\'\n },\n \'description\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'MEDIUMTEXT\'\n },\n \'isactive\',\n {\n \'DEFAULT\' => 1,\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'defaultmilestone\',\n {\n \'DEFAULT\' => \'\\\'---\\\'\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'allows_unconfirmed\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n }\n ],\n \'INDEXES\' => [\n \'products_name_idx\',\n {\n \'FIELDS\' => [\n \'name\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'profile_search\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'INTSERIAL\'\n },\n \'user_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'bug_list\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'MEDIUMTEXT\'\n },\n \'list_order\',\n {\n \'TYPE\' => \'MEDIUMTEXT\'\n }\n ],\n \'INDEXES\' => [\n \'profile_search_user_id_idx\',\n [\n \'user_id\'\n ]\n ]\n },\n \'profile_setting\' => {\n \'FIELDS\' => [\n \'user_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'setting_name\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'name\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'setting\',\n \'created\' => 1\n },\n \'TYPE\' => \'varchar(32)\'\n },\n \'setting_value\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(32)\'\n }\n ],\n \'INDEXES\' => [\n \'profile_setting_value_unique_idx\',\n {\n \'FIELDS\' => [\n \'user_id\',\n \'setting_name\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'profiles\' => {\n \'FIELDS\' => [\n \'userid\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'login_name\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(255)\'\n },\n \'cryptpassword\',\n {\n \'TYPE\' => \'varchar(128)\'\n },\n \'realname\',\n {\n \'DEFAULT\' => \'\\\'\\\'\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(255)\'\n },\n \'disabledtext\',\n {\n \'DEFAULT\' => \'\\\'\\\'\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'MEDIUMTEXT\'\n },\n \'disable_mail\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'mybugslink\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'extern_id\',\n {\n \'TYPE\' => \'varchar(64)\'\n },\n \'is_enabled\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'last_seen_date\',\n {\n \'TYPE\' => \'DATETIME\'\n }\n ],\n \'INDEXES\' => [\n \'profiles_login_name_idx\',\n {\n \'FIELDS\' => [\n \'login_name\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'profiles_extern_id_idx\',\n {\n \'FIELDS\' => [\n \'extern_id\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'profiles_activity\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'userid\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'who\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'profiles_when\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'DATETIME\'\n },\n \'fieldid\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'TABLE\' => \'fielddefs\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'oldvalue\',\n {\n \'TYPE\' => \'TINYTEXT\'\n },\n \'newvalue\',\n {\n \'TYPE\' => \'TINYTEXT\'\n }\n ],\n \'INDEXES\' => [\n \'profiles_activity_userid_idx\',\n [\n \'userid\'\n ],\n \'profiles_activity_profiles_when_idx\',\n [\n \'profiles_when\'\n ],\n \'profiles_activity_fieldid_idx\',\n [\n \'fieldid\'\n ]\n ]\n },\n \'quips\' => {\n \'FIELDS\' => [\n \'quipid\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'userid\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'SET NULL\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'quip\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(512)\'\n },\n \'approved\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n }\n ]\n },\n \'rep_platform\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'SMALLSERIAL\'\n },\n \'value\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'sortkey\',\n {\n \'DEFAULT\' => 0,\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n },\n \'isactive\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'visibility_value_id\',\n {\n \'TYPE\' => \'INT2\'\n }\n ],\n \'INDEXES\' => [\n \'rep_platform_value_idx\',\n {\n \'FIELDS\' => [\n \'value\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'rep_platform_sortkey_idx\',\n [\n \'sortkey\',\n \'value\'\n ],\n \'rep_platform_visibility_value_id_idx\',\n [\n \'visibility_value_id\'\n ]\n ]\n },\n \'reports\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'user_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'name\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'query\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'LONGTEXT\'\n }\n ],\n \'INDEXES\' => [\n \'reports_user_id_idx\',\n {\n \'FIELDS\' => [\n \'user_id\',\n \'name\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'resolution\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'SMALLSERIAL\'\n },\n \'value\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'sortkey\',\n {\n \'DEFAULT\' => 0,\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n },\n \'isactive\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'visibility_value_id\',\n {\n \'TYPE\' => \'INT2\'\n }\n ],\n \'INDEXES\' => [\n \'resolution_value_idx\',\n {\n \'FIELDS\' => [\n \'value\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'resolution_sortkey_idx\',\n [\n \'sortkey\',\n \'value\'\n ],\n \'resolution_visibility_value_id_idx\',\n [\n \'visibility_value_id\'\n ]\n ]\n },\n \'series\' => {\n \'FIELDS\' => [\n \'series_id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'creator\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'category\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'series_categories\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT2\'\n },\n \'subcategory\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'series_categories\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT2\'\n },\n \'name\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'frequency\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n },\n \'query\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'MEDIUMTEXT\'\n },\n \'is_public\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n }\n ],\n \'INDEXES\' => [\n \'series_creator_idx\',\n [\n \'creator\'\n ],\n \'series_category_idx\',\n {\n \'FIELDS\' => [\n \'category\',\n \'subcategory\',\n \'name\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'series_categories\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'SMALLSERIAL\'\n },\n \'name\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n }\n ],\n \'INDEXES\' => [\n \'series_categories_name_idx\',\n {\n \'FIELDS\' => [\n \'name\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'series_data\' => {\n \'FIELDS\' => [\n \'series_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'series_id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'series\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'series_date\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'DATETIME\'\n },\n \'series_value\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'series_data_series_id_idx\',\n {\n \'FIELDS\' => [\n \'series_id\',\n \'series_date\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'setting\' => {\n \'FIELDS\' => [\n \'name\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'varchar(32)\'\n },\n \'default_value\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(32)\'\n },\n \'is_enabled\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'subclass\',\n {\n \'TYPE\' => \'varchar(32)\'\n }\n ]\n },\n \'setting_value\' => {\n \'FIELDS\' => [\n \'name\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'name\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'setting\',\n \'created\' => 1\n },\n \'TYPE\' => \'varchar(32)\'\n },\n \'value\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(32)\'\n },\n \'sortindex\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n }\n ],\n \'INDEXES\' => [\n \'setting_value_nv_unique_idx\',\n {\n \'FIELDS\' => [\n \'name\',\n \'value\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'setting_value_ns_unique_idx\',\n {\n \'FIELDS\' => [\n \'name\',\n \'sortindex\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'status_workflow\' => {\n \'FIELDS\' => [\n \'old_status\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bug_status\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT2\'\n },\n \'new_status\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'bug_status\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT2\'\n },\n \'require_comment\',\n {\n \'DEFAULT\' => 0,\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT1\'\n }\n ],\n \'INDEXES\' => [\n \'status_workflow_idx\',\n {\n \'FIELDS\' => [\n \'old_status\',\n \'new_status\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'tag\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'name\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'user_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'tag_user_id_idx\',\n {\n \'FIELDS\' => [\n \'user_id\',\n \'name\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'tokens\' => {\n \'FIELDS\' => [\n \'userid\',\n {\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'issuedate\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'DATETIME\'\n },\n \'token\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'varchar(16)\'\n },\n \'tokentype\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(16)\'\n },\n \'eventdata\',\n {\n \'TYPE\' => \'TINYTEXT\'\n }\n ],\n \'INDEXES\' => [\n \'tokens_userid_idx\',\n [\n \'userid\'\n ]\n ]\n },\n \'ts_error\' => {\n \'FIELDS\' => [\n \'error_time\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT4\'\n },\n \'jobid\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT4\'\n },\n \'message\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(255)\'\n },\n \'funcid\',\n {\n \'DEFAULT\' => 0,\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT4\'\n }\n ],\n \'INDEXES\' => [\n \'ts_error_funcid_idx\',\n [\n \'funcid\',\n \'error_time\'\n ],\n \'ts_error_error_time_idx\',\n [\n \'error_time\'\n ],\n \'ts_error_jobid_idx\',\n [\n \'jobid\'\n ]\n ]\n },\n \'ts_exitstatus\' => {\n \'FIELDS\' => [\n \'jobid\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'INTSERIAL\'\n },\n \'funcid\',\n {\n \'DEFAULT\' => 0,\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT4\'\n },\n \'status\',\n {\n \'TYPE\' => \'INT2\'\n },\n \'completion_time\',\n {\n \'TYPE\' => \'INT4\'\n },\n \'delete_after\',\n {\n \'TYPE\' => \'INT4\'\n }\n ],\n \'INDEXES\' => [\n \'ts_exitstatus_funcid_idx\',\n [\n \'funcid\'\n ],\n \'ts_exitstatus_delete_after_idx\',\n [\n \'delete_after\'\n ]\n ]\n },\n \'ts_funcmap\' => {\n \'FIELDS\' => [\n \'funcid\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'INTSERIAL\'\n },\n \'funcname\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(255)\'\n }\n ],\n \'INDEXES\' => [\n \'ts_funcmap_funcname_idx\',\n {\n \'FIELDS\' => [\n \'funcname\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'ts_job\' => {\n \'FIELDS\' => [\n \'jobid\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'INTSERIAL\'\n },\n \'funcid\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT4\'\n },\n \'arg\',\n {\n \'TYPE\' => \'LONGBLOB\'\n },\n \'uniqkey\',\n {\n \'TYPE\' => \'varchar(255)\'\n },\n \'insert_time\',\n {\n \'TYPE\' => \'INT4\'\n },\n \'run_after\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT4\'\n },\n \'grabbed_until\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT4\'\n },\n \'priority\',\n {\n \'TYPE\' => \'INT2\'\n },\n \'coalesce\',\n {\n \'TYPE\' => \'varchar(255)\'\n }\n ],\n \'INDEXES\' => [\n \'ts_job_funcid_idx\',\n {\n \'FIELDS\' => [\n \'funcid\',\n \'uniqkey\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'ts_job_run_after_idx\',\n [\n \'run_after\',\n \'funcid\'\n ],\n \'ts_job_coalesce_idx\',\n [\n \'coalesce\',\n \'funcid\'\n ]\n ]\n },\n \'ts_note\' => {\n \'FIELDS\' => [\n \'jobid\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT4\'\n },\n \'notekey\',\n {\n \'TYPE\' => \'varchar(255)\'\n },\n \'value\',\n {\n \'TYPE\' => \'LONGBLOB\'\n }\n ],\n \'INDEXES\' => [\n \'ts_note_jobid_idx\',\n {\n \'FIELDS\' => [\n \'jobid\',\n \'notekey\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'user_api_keys\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'INTSERIAL\'\n },\n \'user_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'api_key\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'VARCHAR(40)\'\n },\n \'description\',\n {\n \'TYPE\' => \'VARCHAR(255)\'\n },\n \'revoked\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'last_used\',\n {\n \'TYPE\' => \'DATETIME\'\n }\n ],\n \'INDEXES\' => [\n \'user_api_keys_api_key_idx\',\n {\n \'FIELDS\' => [\n \'api_key\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'user_api_keys_user_id_idx\',\n [\n \'user_id\'\n ]\n ]\n },\n \'user_group_map\' => {\n \'FIELDS\' => [\n \'user_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'group_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'groups\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'isbless\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'grant_type\',\n {\n \'DEFAULT\' => 0,\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT1\'\n }\n ],\n \'INDEXES\' => [\n \'user_group_map_user_id_idx\',\n {\n \'FIELDS\' => [\n \'user_id\',\n \'group_id\',\n \'grant_type\',\n \'isbless\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'versions\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'value\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'product_id\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'products\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT2\'\n },\n \'isactive\',\n {\n \'DEFAULT\' => \'TRUE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n }\n ],\n \'INDEXES\' => [\n \'versions_product_id_idx\',\n {\n \'FIELDS\' => [\n \'product_id\',\n \'value\'\n ],\n \'TYPE\' => \'UNIQUE\'\n }\n ]\n },\n \'watch\' => {\n \'FIELDS\' => [\n \'watcher\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'watched\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n }\n ],\n \'INDEXES\' => [\n \'watch_watcher_idx\',\n {\n \'FIELDS\' => [\n \'watcher\',\n \'watched\'\n ],\n \'TYPE\' => \'UNIQUE\'\n },\n \'watch_watched_idx\',\n [\n \'watched\'\n ]\n ]\n },\n \'whine_events\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'owner_userid\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'userid\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'profiles\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'subject\',\n {\n \'TYPE\' => \'varchar(128)\'\n },\n \'body\',\n {\n \'TYPE\' => \'MEDIUMTEXT\'\n },\n \'mailifnobugs\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n }\n ]\n },\n \'whine_queries\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'eventid\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'whine_events\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'query_name\',\n {\n \'DEFAULT\' => \'\\\'\\\'\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(64)\'\n },\n \'sortkey\',\n {\n \'DEFAULT\' => \'0\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n },\n \'onemailperbug\',\n {\n \'DEFAULT\' => \'FALSE\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'BOOLEAN\'\n },\n \'title\',\n {\n \'DEFAULT\' => \'\\\'\\\'\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'varchar(128)\'\n }\n ],\n \'INDEXES\' => [\n \'whine_queries_eventid_idx\',\n [\n \'eventid\'\n ]\n ]\n },\n \'whine_schedules\' => {\n \'FIELDS\' => [\n \'id\',\n {\n \'NOTNULL\' => 1,\n \'PRIMARYKEY\' => 1,\n \'TYPE\' => \'MEDIUMSERIAL\'\n },\n \'eventid\',\n {\n \'NOTNULL\' => 1,\n \'REFERENCES\' => {\n \'COLUMN\' => \'id\',\n \'DELETE\' => \'CASCADE\',\n \'TABLE\' => \'whine_events\',\n \'created\' => 1\n },\n \'TYPE\' => \'INT3\'\n },\n \'run_day\',\n {\n \'TYPE\' => \'varchar(32)\'\n },\n \'run_time\',\n {\n \'TYPE\' => \'varchar(32)\'\n },\n \'run_next\',\n {\n \'TYPE\' => \'DATETIME\'\n },\n \'mailto\',\n {\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT3\'\n },\n \'mailto_type\',\n {\n \'DEFAULT\' => \'0\',\n \'NOTNULL\' => 1,\n \'TYPE\' => \'INT2\'\n }\n ],\n \'INDEXES\' => [\n \'whine_schedules_run_next_idx\',\n [\n \'run_next\'\n ],\n \'whine_schedules_eventid_idx\',\n [\n \'eventid\'\n ]\n ]\n }\n };\n',3.00); +/*!40000 ALTER TABLE `bz_schema` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `category_group_map` +-- + +DROP TABLE IF EXISTS `category_group_map`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `category_group_map` ( + `category_id` smallint(6) NOT NULL, + `group_id` mediumint(9) NOT NULL, + UNIQUE KEY `category_group_map_category_id_idx` (`category_id`,`group_id`), + KEY `fk_category_group_map_group_id_groups_id` (`group_id`), + CONSTRAINT `fk_category_group_map_category_id_series_categories_id` FOREIGN KEY (`category_id`) REFERENCES `series_categories` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_category_group_map_group_id_groups_id` FOREIGN KEY (`group_id`) REFERENCES `groups` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `category_group_map` +-- + +LOCK TABLES `category_group_map` WRITE; +/*!40000 ALTER TABLE `category_group_map` DISABLE KEYS */; +/*!40000 ALTER TABLE `category_group_map` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `cc` +-- + +DROP TABLE IF EXISTS `cc`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `cc` ( + `bug_id` mediumint(9) NOT NULL, + `who` mediumint(9) NOT NULL, + UNIQUE KEY `cc_bug_id_idx` (`bug_id`,`who`), + KEY `cc_who_idx` (`who`), + CONSTRAINT `fk_cc_bug_id_bugs_bug_id` FOREIGN KEY (`bug_id`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_cc_who_profiles_userid` FOREIGN KEY (`who`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `cc` +-- + +LOCK TABLES `cc` WRITE; +/*!40000 ALTER TABLE `cc` DISABLE KEYS */; +INSERT INTO `cc` VALUES (2,2); +/*!40000 ALTER TABLE `cc` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `classifications` +-- + +DROP TABLE IF EXISTS `classifications`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `classifications` ( + `id` smallint(6) NOT NULL AUTO_INCREMENT, + `name` varchar(64) NOT NULL, + `description` mediumtext DEFAULT NULL, + `sortkey` smallint(6) NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + UNIQUE KEY `classifications_name_idx` (`name`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `classifications` +-- + +LOCK TABLES `classifications` WRITE; +/*!40000 ALTER TABLE `classifications` DISABLE KEYS */; +INSERT INTO `classifications` VALUES (1,'Unclassified','Not assigned to any classification',0); +/*!40000 ALTER TABLE `classifications` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `component_cc` +-- + +DROP TABLE IF EXISTS `component_cc`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `component_cc` ( + `user_id` mediumint(9) NOT NULL, + `component_id` mediumint(9) NOT NULL, + UNIQUE KEY `component_cc_user_id_idx` (`component_id`,`user_id`), + KEY `fk_component_cc_user_id_profiles_userid` (`user_id`), + CONSTRAINT `fk_component_cc_component_id_components_id` FOREIGN KEY (`component_id`) REFERENCES `components` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_component_cc_user_id_profiles_userid` FOREIGN KEY (`user_id`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `component_cc` +-- + +LOCK TABLES `component_cc` WRITE; +/*!40000 ALTER TABLE `component_cc` DISABLE KEYS */; +/*!40000 ALTER TABLE `component_cc` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `components` +-- + +DROP TABLE IF EXISTS `components`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `components` ( + `id` mediumint(9) NOT NULL AUTO_INCREMENT, + `name` varchar(64) NOT NULL, + `product_id` smallint(6) NOT NULL, + `initialowner` mediumint(9) NOT NULL, + `initialqacontact` mediumint(9) DEFAULT NULL, + `description` mediumtext NOT NULL, + `isactive` tinyint(4) NOT NULL DEFAULT 1, + PRIMARY KEY (`id`), + UNIQUE KEY `components_product_id_idx` (`product_id`,`name`), + KEY `components_name_idx` (`name`), + KEY `fk_components_initialqacontact_profiles_userid` (`initialqacontact`), + KEY `fk_components_initialowner_profiles_userid` (`initialowner`), + CONSTRAINT `fk_components_initialowner_profiles_userid` FOREIGN KEY (`initialowner`) REFERENCES `profiles` (`userid`) ON UPDATE CASCADE, + CONSTRAINT `fk_components_initialqacontact_profiles_userid` FOREIGN KEY (`initialqacontact`) REFERENCES `profiles` (`userid`) ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT `fk_components_product_id_products_id` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `components` +-- + +LOCK TABLES `components` WRITE; +/*!40000 ALTER TABLE `components` DISABLE KEYS */; +INSERT INTO `components` VALUES (1,'TestComponent',1,1,NULL,'This is a test component in the test product database. This ought to be blown away and replaced with real stuff in a finished installation of Bugzilla.',1),(2,'python-bugzilla',2,1,NULL,'Lorem ipsum dolor sit amet',1),(3,'Kernel',3,1,NULL,'Lorem ipsum',1),(4,'Containers',3,1,NULL,'Lorem ipsum',1); +/*!40000 ALTER TABLE `components` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `dependencies` +-- + +DROP TABLE IF EXISTS `dependencies`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `dependencies` ( + `blocked` mediumint(9) NOT NULL, + `dependson` mediumint(9) NOT NULL, + UNIQUE KEY `dependencies_blocked_idx` (`blocked`,`dependson`), + KEY `dependencies_dependson_idx` (`dependson`), + CONSTRAINT `fk_dependencies_blocked_bugs_bug_id` FOREIGN KEY (`blocked`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_dependencies_dependson_bugs_bug_id` FOREIGN KEY (`dependson`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `dependencies` +-- + +LOCK TABLES `dependencies` WRITE; +/*!40000 ALTER TABLE `dependencies` DISABLE KEYS */; +/*!40000 ALTER TABLE `dependencies` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `duplicates` +-- + +DROP TABLE IF EXISTS `duplicates`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `duplicates` ( + `dupe_of` mediumint(9) NOT NULL, + `dupe` mediumint(9) NOT NULL, + PRIMARY KEY (`dupe`), + KEY `fk_duplicates_dupe_of_bugs_bug_id` (`dupe_of`), + CONSTRAINT `fk_duplicates_dupe_bugs_bug_id` FOREIGN KEY (`dupe`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_duplicates_dupe_of_bugs_bug_id` FOREIGN KEY (`dupe_of`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `duplicates` +-- + +LOCK TABLES `duplicates` WRITE; +/*!40000 ALTER TABLE `duplicates` DISABLE KEYS */; +/*!40000 ALTER TABLE `duplicates` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `email_bug_ignore` +-- + +DROP TABLE IF EXISTS `email_bug_ignore`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `email_bug_ignore` ( + `user_id` mediumint(9) NOT NULL, + `bug_id` mediumint(9) NOT NULL, + UNIQUE KEY `email_bug_ignore_user_id_idx` (`user_id`,`bug_id`), + KEY `fk_email_bug_ignore_bug_id_bugs_bug_id` (`bug_id`), + CONSTRAINT `fk_email_bug_ignore_bug_id_bugs_bug_id` FOREIGN KEY (`bug_id`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_email_bug_ignore_user_id_profiles_userid` FOREIGN KEY (`user_id`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `email_bug_ignore` +-- + +LOCK TABLES `email_bug_ignore` WRITE; +/*!40000 ALTER TABLE `email_bug_ignore` DISABLE KEYS */; +/*!40000 ALTER TABLE `email_bug_ignore` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `email_setting` +-- + +DROP TABLE IF EXISTS `email_setting`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `email_setting` ( + `user_id` mediumint(9) NOT NULL, + `relationship` tinyint(4) NOT NULL, + `event` tinyint(4) NOT NULL, + UNIQUE KEY `email_setting_user_id_idx` (`user_id`,`relationship`,`event`), + CONSTRAINT `fk_email_setting_user_id_profiles_userid` FOREIGN KEY (`user_id`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `email_setting` +-- + +LOCK TABLES `email_setting` WRITE; +/*!40000 ALTER TABLE `email_setting` DISABLE KEYS */; +INSERT INTO `email_setting` VALUES (1,0,0),(1,0,1),(1,0,2),(1,0,3),(1,0,4),(1,0,5),(1,0,6),(1,0,7),(1,0,9),(1,0,10),(1,0,11),(1,0,50),(1,1,0),(1,1,1),(1,1,2),(1,1,3),(1,1,4),(1,1,5),(1,1,6),(1,1,7),(1,1,9),(1,1,10),(1,1,11),(1,1,50),(1,2,0),(1,2,1),(1,2,2),(1,2,3),(1,2,4),(1,2,5),(1,2,6),(1,2,7),(1,2,8),(1,2,9),(1,2,10),(1,2,11),(1,2,50),(1,3,0),(1,3,1),(1,3,2),(1,3,3),(1,3,4),(1,3,5),(1,3,6),(1,3,7),(1,3,9),(1,3,10),(1,3,11),(1,3,50),(1,5,0),(1,5,1),(1,5,2),(1,5,3),(1,5,4),(1,5,5),(1,5,6),(1,5,7),(1,5,9),(1,5,10),(1,5,11),(1,5,50),(1,100,100),(1,100,101),(2,0,0),(2,0,1),(2,0,2),(2,0,3),(2,0,4),(2,0,5),(2,0,6),(2,0,7),(2,0,9),(2,0,10),(2,0,11),(2,0,50),(2,1,0),(2,1,1),(2,1,2),(2,1,3),(2,1,4),(2,1,5),(2,1,6),(2,1,7),(2,1,9),(2,1,10),(2,1,11),(2,1,50),(2,2,0),(2,2,1),(2,2,2),(2,2,3),(2,2,4),(2,2,5),(2,2,6),(2,2,7),(2,2,8),(2,2,9),(2,2,10),(2,2,11),(2,2,50),(2,3,0),(2,3,1),(2,3,2),(2,3,3),(2,3,4),(2,3,5),(2,3,6),(2,3,7),(2,3,9),(2,3,10),(2,3,11),(2,3,50),(2,5,0),(2,5,1),(2,5,2),(2,5,3),(2,5,4),(2,5,5),(2,5,6),(2,5,7),(2,5,9),(2,5,10),(2,5,11),(2,5,50),(2,100,100),(2,100,101); +/*!40000 ALTER TABLE `email_setting` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `field_visibility` +-- + +DROP TABLE IF EXISTS `field_visibility`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `field_visibility` ( + `field_id` mediumint(9) DEFAULT NULL, + `value_id` smallint(6) NOT NULL, + UNIQUE KEY `field_visibility_field_id_idx` (`field_id`,`value_id`), + CONSTRAINT `fk_field_visibility_field_id_fielddefs_id` FOREIGN KEY (`field_id`) REFERENCES `fielddefs` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `field_visibility` +-- + +LOCK TABLES `field_visibility` WRITE; +/*!40000 ALTER TABLE `field_visibility` DISABLE KEYS */; +/*!40000 ALTER TABLE `field_visibility` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `fielddefs` +-- + +DROP TABLE IF EXISTS `fielddefs`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `fielddefs` ( + `id` mediumint(9) NOT NULL AUTO_INCREMENT, + `name` varchar(64) NOT NULL, + `type` smallint(6) NOT NULL DEFAULT 0, + `custom` tinyint(4) NOT NULL DEFAULT 0, + `description` tinytext NOT NULL, + `long_desc` varchar(255) NOT NULL DEFAULT '', + `mailhead` tinyint(4) NOT NULL DEFAULT 0, + `sortkey` smallint(6) NOT NULL, + `obsolete` tinyint(4) NOT NULL DEFAULT 0, + `enter_bug` tinyint(4) NOT NULL DEFAULT 0, + `buglist` tinyint(4) NOT NULL DEFAULT 0, + `visibility_field_id` mediumint(9) DEFAULT NULL, + `value_field_id` mediumint(9) DEFAULT NULL, + `reverse_desc` tinytext DEFAULT NULL, + `is_mandatory` tinyint(4) NOT NULL DEFAULT 0, + `is_numeric` tinyint(4) NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + UNIQUE KEY `fielddefs_name_idx` (`name`), + KEY `fielddefs_sortkey_idx` (`sortkey`), + KEY `fielddefs_value_field_id_idx` (`value_field_id`), + KEY `fielddefs_is_mandatory_idx` (`is_mandatory`), + KEY `fk_fielddefs_visibility_field_id_fielddefs_id` (`visibility_field_id`), + CONSTRAINT `fk_fielddefs_value_field_id_fielddefs_id` FOREIGN KEY (`value_field_id`) REFERENCES `fielddefs` (`id`) ON UPDATE CASCADE, + CONSTRAINT `fk_fielddefs_visibility_field_id_fielddefs_id` FOREIGN KEY (`visibility_field_id`) REFERENCES `fielddefs` (`id`) ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=60 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `fielddefs` +-- + +LOCK TABLES `fielddefs` WRITE; +/*!40000 ALTER TABLE `fielddefs` DISABLE KEYS */; +INSERT INTO `fielddefs` VALUES (1,'bug_id',0,0,'Bug #','',1,100,0,0,1,NULL,NULL,NULL,0,1),(2,'short_desc',0,0,'Summary','',1,200,0,0,1,NULL,NULL,NULL,1,0),(3,'classification',2,0,'Classification','',1,300,0,0,1,NULL,NULL,NULL,0,0),(4,'product',2,0,'Product','',1,400,0,0,1,NULL,NULL,NULL,1,0),(5,'version',0,0,'Version','',1,500,0,0,1,NULL,NULL,NULL,1,0),(6,'rep_platform',2,0,'Platform','',1,600,0,0,1,NULL,NULL,NULL,0,0),(7,'bug_file_loc',0,0,'URL','',1,700,0,0,1,NULL,NULL,NULL,0,0),(8,'op_sys',2,0,'OS/Version','',1,800,0,0,1,NULL,NULL,NULL,0,0),(9,'bug_status',2,0,'Status','',1,900,0,0,1,NULL,NULL,NULL,0,0),(10,'status_whiteboard',0,0,'Status Whiteboard','',1,1000,0,0,1,NULL,NULL,NULL,0,0),(11,'keywords',8,0,'Keywords','',1,1100,0,0,1,NULL,NULL,NULL,0,0),(12,'resolution',2,0,'Resolution','',0,1200,0,0,1,NULL,NULL,NULL,0,0),(13,'bug_severity',2,0,'Severity','',1,1300,0,0,1,NULL,NULL,NULL,0,0),(14,'priority',2,0,'Priority','',1,1400,0,0,1,NULL,NULL,NULL,0,0),(15,'component',2,0,'Component','',1,1500,0,0,1,NULL,NULL,NULL,1,0),(16,'assigned_to',0,0,'AssignedTo','',1,1600,0,0,1,NULL,NULL,NULL,0,0),(17,'reporter',0,0,'ReportedBy','',1,1700,0,0,1,NULL,NULL,NULL,0,0),(18,'qa_contact',0,0,'QAContact','',1,1800,0,0,1,NULL,NULL,NULL,0,0),(19,'assigned_to_realname',0,0,'AssignedToName','',0,1900,0,0,1,NULL,NULL,NULL,0,0),(20,'reporter_realname',0,0,'ReportedByName','',0,2000,0,0,1,NULL,NULL,NULL,0,0),(21,'qa_contact_realname',0,0,'QAContactName','',0,2100,0,0,1,NULL,NULL,NULL,0,0),(22,'cc',0,0,'CC','',1,2200,0,0,0,NULL,NULL,NULL,0,0),(23,'dependson',0,0,'Depends on','',1,2300,0,0,1,NULL,NULL,NULL,0,1),(24,'blocked',0,0,'Blocks','',1,2400,0,0,1,NULL,NULL,NULL,0,1),(25,'attachments.description',0,0,'Attachment description','',0,2500,0,0,0,NULL,NULL,NULL,0,0),(26,'attachments.filename',0,0,'Attachment filename','',0,2600,0,0,0,NULL,NULL,NULL,0,0),(27,'attachments.mimetype',0,0,'Attachment mime type','',0,2700,0,0,0,NULL,NULL,NULL,0,0),(28,'attachments.ispatch',0,0,'Attachment is patch','',0,2800,0,0,0,NULL,NULL,NULL,0,1),(29,'attachments.isobsolete',0,0,'Attachment is obsolete','',0,2900,0,0,0,NULL,NULL,NULL,0,1),(30,'attachments.isprivate',0,0,'Attachment is private','',0,3000,0,0,0,NULL,NULL,NULL,0,1),(31,'attachments.submitter',0,0,'Attachment creator','',0,3100,0,0,0,NULL,NULL,NULL,0,0),(32,'target_milestone',0,0,'Target Milestone','',1,3200,0,0,1,NULL,NULL,NULL,0,0),(33,'creation_ts',0,0,'Creation date','',0,3300,0,0,1,NULL,NULL,NULL,0,0),(34,'delta_ts',0,0,'Last changed date','',0,3400,0,0,1,NULL,NULL,NULL,0,0),(35,'longdesc',0,0,'Comment','',0,3500,0,0,0,NULL,NULL,NULL,0,0),(36,'longdescs.isprivate',0,0,'Comment is private','',0,3600,0,0,0,NULL,NULL,NULL,0,1),(37,'longdescs.count',0,0,'Number of Comments','',0,3700,0,0,1,NULL,NULL,NULL,0,1),(38,'alias',0,0,'Alias','',0,3800,0,0,1,NULL,NULL,NULL,0,0),(39,'everconfirmed',0,0,'Ever Confirmed','',0,3900,0,0,0,NULL,NULL,NULL,0,1),(40,'reporter_accessible',0,0,'Reporter Accessible','',0,4000,0,0,0,NULL,NULL,NULL,0,1),(41,'cclist_accessible',0,0,'CC Accessible','',0,4100,0,0,0,NULL,NULL,NULL,0,1),(42,'bug_group',0,0,'Group','',1,4200,0,0,0,NULL,NULL,NULL,0,0),(43,'estimated_time',0,0,'Estimated Hours','',1,4300,0,0,1,NULL,NULL,NULL,0,1),(44,'remaining_time',0,0,'Remaining Hours','',0,4400,0,0,1,NULL,NULL,NULL,0,1),(45,'deadline',5,0,'Deadline','',1,4500,0,0,1,NULL,NULL,NULL,0,0),(46,'commenter',0,0,'Commenter','',0,4600,0,0,0,NULL,NULL,NULL,0,0),(47,'flagtypes.name',0,0,'Flags','',0,4700,0,0,1,NULL,NULL,NULL,0,0),(48,'requestees.login_name',0,0,'Flag Requestee','',0,4800,0,0,0,NULL,NULL,NULL,0,0),(49,'setters.login_name',0,0,'Flag Setter','',0,4900,0,0,0,NULL,NULL,NULL,0,0),(50,'work_time',0,0,'Hours Worked','',0,5000,0,0,1,NULL,NULL,NULL,0,1),(51,'percentage_complete',0,0,'Percentage Complete','',0,5100,0,0,1,NULL,NULL,NULL,0,1),(52,'content',0,0,'Content','',0,5200,0,0,0,NULL,NULL,NULL,0,0),(53,'attach_data.thedata',0,0,'Attachment data','',0,5300,0,0,0,NULL,NULL,NULL,0,0),(54,'owner_idle_time',0,0,'Time Since Assignee Touched','',0,5400,0,0,0,NULL,NULL,NULL,0,0),(55,'see_also',7,0,'See Also','',0,5500,0,0,0,NULL,NULL,NULL,0,0),(56,'tag',8,0,'Personal Tags','',0,5600,0,0,1,NULL,NULL,NULL,0,0),(57,'last_visit_ts',5,0,'Last Visit','',0,5700,0,0,1,NULL,NULL,NULL,0,0),(58,'comment_tag',0,0,'Comment Tag','',0,5800,0,0,0,NULL,NULL,NULL,0,0),(59,'days_elapsed',0,0,'Days since bug changed','',0,5900,0,0,0,NULL,NULL,NULL,0,0); +/*!40000 ALTER TABLE `fielddefs` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `flagexclusions` +-- + +DROP TABLE IF EXISTS `flagexclusions`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `flagexclusions` ( + `type_id` mediumint(9) NOT NULL, + `product_id` smallint(6) DEFAULT NULL, + `component_id` mediumint(9) DEFAULT NULL, + UNIQUE KEY `flagexclusions_type_id_idx` (`type_id`,`product_id`,`component_id`), + KEY `fk_flagexclusions_component_id_components_id` (`component_id`), + KEY `fk_flagexclusions_product_id_products_id` (`product_id`), + CONSTRAINT `fk_flagexclusions_component_id_components_id` FOREIGN KEY (`component_id`) REFERENCES `components` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_flagexclusions_product_id_products_id` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_flagexclusions_type_id_flagtypes_id` FOREIGN KEY (`type_id`) REFERENCES `flagtypes` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `flagexclusions` +-- + +LOCK TABLES `flagexclusions` WRITE; +/*!40000 ALTER TABLE `flagexclusions` DISABLE KEYS */; +/*!40000 ALTER TABLE `flagexclusions` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `flaginclusions` +-- + +DROP TABLE IF EXISTS `flaginclusions`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `flaginclusions` ( + `type_id` mediumint(9) NOT NULL, + `product_id` smallint(6) DEFAULT NULL, + `component_id` mediumint(9) DEFAULT NULL, + UNIQUE KEY `flaginclusions_type_id_idx` (`type_id`,`product_id`,`component_id`), + KEY `fk_flaginclusions_component_id_components_id` (`component_id`), + KEY `fk_flaginclusions_product_id_products_id` (`product_id`), + CONSTRAINT `fk_flaginclusions_component_id_components_id` FOREIGN KEY (`component_id`) REFERENCES `components` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_flaginclusions_product_id_products_id` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_flaginclusions_type_id_flagtypes_id` FOREIGN KEY (`type_id`) REFERENCES `flagtypes` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `flaginclusions` +-- + +LOCK TABLES `flaginclusions` WRITE; +/*!40000 ALTER TABLE `flaginclusions` DISABLE KEYS */; +INSERT INTO `flaginclusions` VALUES (1,NULL,NULL); +/*!40000 ALTER TABLE `flaginclusions` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `flags` +-- + +DROP TABLE IF EXISTS `flags`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `flags` ( + `id` mediumint(9) NOT NULL AUTO_INCREMENT, + `type_id` mediumint(9) NOT NULL, + `status` char(1) NOT NULL, + `bug_id` mediumint(9) NOT NULL, + `attach_id` mediumint(9) DEFAULT NULL, + `creation_date` datetime NOT NULL, + `modification_date` datetime DEFAULT NULL, + `setter_id` mediumint(9) NOT NULL, + `requestee_id` mediumint(9) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `flags_bug_id_idx` (`bug_id`,`attach_id`), + KEY `flags_setter_id_idx` (`setter_id`), + KEY `flags_requestee_id_idx` (`requestee_id`), + KEY `flags_type_id_idx` (`type_id`), + KEY `fk_flags_attach_id_attachments_attach_id` (`attach_id`), + CONSTRAINT `fk_flags_attach_id_attachments_attach_id` FOREIGN KEY (`attach_id`) REFERENCES `attachments` (`attach_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_flags_bug_id_bugs_bug_id` FOREIGN KEY (`bug_id`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_flags_requestee_id_profiles_userid` FOREIGN KEY (`requestee_id`) REFERENCES `profiles` (`userid`) ON UPDATE CASCADE, + CONSTRAINT `fk_flags_setter_id_profiles_userid` FOREIGN KEY (`setter_id`) REFERENCES `profiles` (`userid`) ON UPDATE CASCADE, + CONSTRAINT `fk_flags_type_id_flagtypes_id` FOREIGN KEY (`type_id`) REFERENCES `flagtypes` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `flags` +-- + +LOCK TABLES `flags` WRITE; +/*!40000 ALTER TABLE `flags` DISABLE KEYS */; +INSERT INTO `flags` VALUES (1,1,'?',2,NULL,'2024-10-15 13:29:13','2024-10-15 13:29:13',1,2); +/*!40000 ALTER TABLE `flags` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `flagtypes` +-- + +DROP TABLE IF EXISTS `flagtypes`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `flagtypes` ( + `id` mediumint(9) NOT NULL AUTO_INCREMENT, + `name` varchar(50) NOT NULL, + `description` mediumtext NOT NULL, + `cc_list` varchar(200) DEFAULT NULL, + `target_type` char(1) NOT NULL DEFAULT 'b', + `is_active` tinyint(4) NOT NULL DEFAULT 1, + `is_requestable` tinyint(4) NOT NULL DEFAULT 0, + `is_requesteeble` tinyint(4) NOT NULL DEFAULT 0, + `is_multiplicable` tinyint(4) NOT NULL DEFAULT 0, + `sortkey` smallint(6) NOT NULL DEFAULT 0, + `grant_group_id` mediumint(9) DEFAULT NULL, + `request_group_id` mediumint(9) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `fk_flagtypes_request_group_id_groups_id` (`request_group_id`), + KEY `fk_flagtypes_grant_group_id_groups_id` (`grant_group_id`), + CONSTRAINT `fk_flagtypes_grant_group_id_groups_id` FOREIGN KEY (`grant_group_id`) REFERENCES `groups` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT `fk_flagtypes_request_group_id_groups_id` FOREIGN KEY (`request_group_id`) REFERENCES `groups` (`id`) ON DELETE SET NULL ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `flagtypes` +-- + +LOCK TABLES `flagtypes` WRITE; +/*!40000 ALTER TABLE `flagtypes` DISABLE KEYS */; +INSERT INTO `flagtypes` VALUES (1,'needinfo','Need more Info','','b',1,1,1,1,0,NULL,NULL); +/*!40000 ALTER TABLE `flagtypes` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `group_control_map` +-- + +DROP TABLE IF EXISTS `group_control_map`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `group_control_map` ( + `group_id` mediumint(9) NOT NULL, + `product_id` smallint(6) NOT NULL, + `entry` tinyint(4) NOT NULL DEFAULT 0, + `membercontrol` tinyint(4) NOT NULL DEFAULT 0, + `othercontrol` tinyint(4) NOT NULL DEFAULT 0, + `canedit` tinyint(4) NOT NULL DEFAULT 0, + `editcomponents` tinyint(4) NOT NULL DEFAULT 0, + `editbugs` tinyint(4) NOT NULL DEFAULT 0, + `canconfirm` tinyint(4) NOT NULL DEFAULT 0, + UNIQUE KEY `group_control_map_product_id_idx` (`product_id`,`group_id`), + KEY `group_control_map_group_id_idx` (`group_id`), + CONSTRAINT `fk_group_control_map_group_id_groups_id` FOREIGN KEY (`group_id`) REFERENCES `groups` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_group_control_map_product_id_products_id` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `group_control_map` +-- + +LOCK TABLES `group_control_map` WRITE; +/*!40000 ALTER TABLE `group_control_map` DISABLE KEYS */; +/*!40000 ALTER TABLE `group_control_map` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `group_group_map` +-- + +DROP TABLE IF EXISTS `group_group_map`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `group_group_map` ( + `member_id` mediumint(9) NOT NULL, + `grantor_id` mediumint(9) NOT NULL, + `grant_type` tinyint(4) NOT NULL DEFAULT 0, + UNIQUE KEY `group_group_map_member_id_idx` (`member_id`,`grantor_id`,`grant_type`), + KEY `fk_group_group_map_grantor_id_groups_id` (`grantor_id`), + CONSTRAINT `fk_group_group_map_grantor_id_groups_id` FOREIGN KEY (`grantor_id`) REFERENCES `groups` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_group_group_map_member_id_groups_id` FOREIGN KEY (`member_id`) REFERENCES `groups` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `group_group_map` +-- + +LOCK TABLES `group_group_map` WRITE; +/*!40000 ALTER TABLE `group_group_map` DISABLE KEYS */; +INSERT INTO `group_group_map` VALUES (1,1,0),(1,1,1),(1,1,2),(1,2,0),(1,2,1),(1,2,2),(1,3,0),(1,3,1),(1,3,2),(1,4,0),(1,4,1),(1,4,2),(1,5,0),(1,5,1),(1,5,2),(1,6,0),(1,6,1),(1,6,2),(1,7,0),(1,7,1),(1,7,2),(1,8,0),(1,8,1),(1,8,2),(1,9,0),(1,9,1),(1,9,2),(1,10,0),(1,10,1),(1,10,2),(1,11,0),(1,11,1),(1,11,2),(8,11,0),(10,11,0),(1,12,0),(1,12,1),(1,12,2),(1,13,0),(1,13,1),(1,13,2),(12,13,0),(1,14,0),(1,14,1),(1,14,2); +/*!40000 ALTER TABLE `group_group_map` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `groups` +-- + +DROP TABLE IF EXISTS `groups`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `groups` ( + `id` mediumint(9) NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + `description` mediumtext NOT NULL, + `isbuggroup` tinyint(4) NOT NULL, + `userregexp` tinytext NOT NULL DEFAULT '', + `isactive` tinyint(4) NOT NULL DEFAULT 1, + `icon_url` tinytext DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `groups_name_idx` (`name`) +) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `groups` +-- + +LOCK TABLES `groups` WRITE; +/*!40000 ALTER TABLE `groups` DISABLE KEYS */; +INSERT INTO `groups` VALUES (1,'admin','Administrators',0,'',1,NULL),(2,'tweakparams','Can change Parameters',0,'',1,NULL),(3,'editusers','Can edit or disable users',0,'',1,NULL),(4,'creategroups','Can create and destroy groups',0,'',1,NULL),(5,'editclassifications','Can create, destroy, and edit classifications',0,'',1,NULL),(6,'editcomponents','Can create, destroy, and edit components',0,'',1,NULL),(7,'editkeywords','Can create, destroy, and edit keywords',0,'',1,NULL),(8,'editbugs','Can edit all bug fields',0,'.*',1,NULL),(9,'canconfirm','Can confirm a bug or mark it a duplicate',0,'',1,NULL),(10,'bz_canusewhineatothers','Can configure whine reports for other users',0,'',1,NULL),(11,'bz_canusewhines','User can configure whine reports for self',0,'',1,NULL),(12,'bz_sudoers','Can perform actions as other users',0,'',1,NULL),(13,'bz_sudo_protect','Can not be impersonated by other users',0,'',1,NULL),(14,'bz_quip_moderators','Can moderate quips',0,'',1,NULL); +/*!40000 ALTER TABLE `groups` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `keyworddefs` +-- + +DROP TABLE IF EXISTS `keyworddefs`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `keyworddefs` ( + `id` smallint(6) NOT NULL AUTO_INCREMENT, + `name` varchar(64) NOT NULL, + `description` mediumtext NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `keyworddefs_name_idx` (`name`) +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `keyworddefs` +-- + +LOCK TABLES `keyworddefs` WRITE; +/*!40000 ALTER TABLE `keyworddefs` DISABLE KEYS */; +INSERT INTO `keyworddefs` VALUES (1,'FooBar','This needs no explanation'),(2,'LoremIpsum','dolor sit amet ...'); +/*!40000 ALTER TABLE `keyworddefs` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `keywords` +-- + +DROP TABLE IF EXISTS `keywords`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `keywords` ( + `bug_id` mediumint(9) NOT NULL, + `keywordid` smallint(6) NOT NULL, + UNIQUE KEY `keywords_bug_id_idx` (`bug_id`,`keywordid`), + KEY `keywords_keywordid_idx` (`keywordid`), + CONSTRAINT `fk_keywords_bug_id_bugs_bug_id` FOREIGN KEY (`bug_id`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_keywords_keywordid_keyworddefs_id` FOREIGN KEY (`keywordid`) REFERENCES `keyworddefs` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `keywords` +-- + +LOCK TABLES `keywords` WRITE; +/*!40000 ALTER TABLE `keywords` DISABLE KEYS */; +INSERT INTO `keywords` VALUES (2,1); +/*!40000 ALTER TABLE `keywords` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `login_failure` +-- + +DROP TABLE IF EXISTS `login_failure`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `login_failure` ( + `user_id` mediumint(9) NOT NULL, + `login_time` datetime NOT NULL, + `ip_addr` varchar(40) NOT NULL, + KEY `login_failure_user_id_idx` (`user_id`), + CONSTRAINT `fk_login_failure_user_id_profiles_userid` FOREIGN KEY (`user_id`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `login_failure` +-- + +LOCK TABLES `login_failure` WRITE; +/*!40000 ALTER TABLE `login_failure` DISABLE KEYS */; +/*!40000 ALTER TABLE `login_failure` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `logincookies` +-- + +DROP TABLE IF EXISTS `logincookies`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `logincookies` ( + `cookie` varchar(16) NOT NULL, + `userid` mediumint(9) NOT NULL, + `ipaddr` varchar(40) DEFAULT NULL, + `lastused` datetime NOT NULL, + PRIMARY KEY (`cookie`), + KEY `logincookies_lastused_idx` (`lastused`), + KEY `fk_logincookies_userid_profiles_userid` (`userid`), + CONSTRAINT `fk_logincookies_userid_profiles_userid` FOREIGN KEY (`userid`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `logincookies` +-- + +LOCK TABLES `logincookies` WRITE; +/*!40000 ALTER TABLE `logincookies` DISABLE KEYS */; +INSERT INTO `logincookies` VALUES ('StQdHXDOZ2',1,NULL,'2024-10-15 14:02:53'); +/*!40000 ALTER TABLE `logincookies` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `longdescs` +-- + +DROP TABLE IF EXISTS `longdescs`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `longdescs` ( + `comment_id` int(11) NOT NULL AUTO_INCREMENT, + `bug_id` mediumint(9) NOT NULL, + `who` mediumint(9) NOT NULL, + `bug_when` datetime NOT NULL, + `work_time` decimal(7,2) NOT NULL DEFAULT 0.00, + `thetext` mediumtext NOT NULL, + `isprivate` tinyint(4) NOT NULL DEFAULT 0, + `already_wrapped` tinyint(4) NOT NULL DEFAULT 0, + `type` smallint(6) NOT NULL DEFAULT 0, + `extra_data` varchar(255) DEFAULT NULL, + PRIMARY KEY (`comment_id`), + KEY `longdescs_bug_id_idx` (`bug_id`,`work_time`), + KEY `longdescs_who_idx` (`who`,`bug_id`), + KEY `longdescs_bug_when_idx` (`bug_when`), + CONSTRAINT `fk_longdescs_bug_id_bugs_bug_id` FOREIGN KEY (`bug_id`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_longdescs_who_profiles_userid` FOREIGN KEY (`who`) REFERENCES `profiles` (`userid`) ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `longdescs` +-- + +LOCK TABLES `longdescs` WRITE; +/*!40000 ALTER TABLE `longdescs` DISABLE KEYS */; +INSERT INTO `longdescs` VALUES (1,1,1,'2023-11-27 15:35:33',0.00,'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\n\nAt vero eos et accusam et justo duo dolores et ea rebum.',0,0,0,NULL),(2,1,1,'2023-11-27 15:37:05',0.00,'Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.',0,0,0,NULL),(3,2,1,'2023-11-27 15:38:45',0.00,'Nobody expects the Spanish Inquisition! \n\nOur chief weapon is surprise, surprise and fear, fear and surprise. \n\nOur two weapons are fear and surprise, and ruthless efficiency. \n\nOur three weapons are fear and surprise and ruthless efficiency and an almost fanatical dedication to the pope.',0,0,0,NULL),(4,3,1,'2024-10-15 13:45:40',0.00,'lorem ipsum dolor sit amet',0,0,0,NULL); +/*!40000 ALTER TABLE `longdescs` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `longdescs_tags` +-- + +DROP TABLE IF EXISTS `longdescs_tags`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `longdescs_tags` ( + `id` mediumint(9) NOT NULL AUTO_INCREMENT, + `comment_id` int(11) DEFAULT NULL, + `tag` varchar(24) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `longdescs_tags_idx` (`comment_id`,`tag`), + CONSTRAINT `fk_longdescs_tags_comment_id_longdescs_comment_id` FOREIGN KEY (`comment_id`) REFERENCES `longdescs` (`comment_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `longdescs_tags` +-- + +LOCK TABLES `longdescs_tags` WRITE; +/*!40000 ALTER TABLE `longdescs_tags` DISABLE KEYS */; +/*!40000 ALTER TABLE `longdescs_tags` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `longdescs_tags_activity` +-- + +DROP TABLE IF EXISTS `longdescs_tags_activity`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `longdescs_tags_activity` ( + `id` mediumint(9) NOT NULL AUTO_INCREMENT, + `bug_id` mediumint(9) NOT NULL, + `comment_id` int(11) DEFAULT NULL, + `who` mediumint(9) NOT NULL, + `bug_when` datetime NOT NULL, + `added` varchar(24) DEFAULT NULL, + `removed` varchar(24) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `longdescs_tags_activity_bug_id_idx` (`bug_id`), + KEY `fk_longdescs_tags_activity_comment_id_longdescs_comment_id` (`comment_id`), + KEY `fk_longdescs_tags_activity_who_profiles_userid` (`who`), + CONSTRAINT `fk_longdescs_tags_activity_bug_id_bugs_bug_id` FOREIGN KEY (`bug_id`) REFERENCES `bugs` (`bug_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_longdescs_tags_activity_comment_id_longdescs_comment_id` FOREIGN KEY (`comment_id`) REFERENCES `longdescs` (`comment_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_longdescs_tags_activity_who_profiles_userid` FOREIGN KEY (`who`) REFERENCES `profiles` (`userid`) ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `longdescs_tags_activity` +-- + +LOCK TABLES `longdescs_tags_activity` WRITE; +/*!40000 ALTER TABLE `longdescs_tags_activity` DISABLE KEYS */; +/*!40000 ALTER TABLE `longdescs_tags_activity` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `longdescs_tags_weights` +-- + +DROP TABLE IF EXISTS `longdescs_tags_weights`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `longdescs_tags_weights` ( + `id` mediumint(9) NOT NULL AUTO_INCREMENT, + `tag` varchar(24) NOT NULL, + `weight` mediumint(9) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `longdescs_tags_weights_tag_idx` (`tag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `longdescs_tags_weights` +-- + +LOCK TABLES `longdescs_tags_weights` WRITE; +/*!40000 ALTER TABLE `longdescs_tags_weights` DISABLE KEYS */; +/*!40000 ALTER TABLE `longdescs_tags_weights` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `mail_staging` +-- + +DROP TABLE IF EXISTS `mail_staging`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `mail_staging` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `message` longblob NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `mail_staging` +-- + +LOCK TABLES `mail_staging` WRITE; +/*!40000 ALTER TABLE `mail_staging` DISABLE KEYS */; +/*!40000 ALTER TABLE `mail_staging` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `milestones` +-- + +DROP TABLE IF EXISTS `milestones`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `milestones` ( + `id` mediumint(9) NOT NULL AUTO_INCREMENT, + `product_id` smallint(6) NOT NULL, + `value` varchar(64) NOT NULL, + `sortkey` smallint(6) NOT NULL DEFAULT 0, + `isactive` tinyint(4) NOT NULL DEFAULT 1, + PRIMARY KEY (`id`), + UNIQUE KEY `milestones_product_id_idx` (`product_id`,`value`), + CONSTRAINT `fk_milestones_product_id_products_id` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `milestones` +-- + +LOCK TABLES `milestones` WRITE; +/*!40000 ALTER TABLE `milestones` DISABLE KEYS */; +INSERT INTO `milestones` VALUES (1,1,'---',0,1),(2,2,'---',0,1),(3,3,'---',0,1); +/*!40000 ALTER TABLE `milestones` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `namedqueries` +-- + +DROP TABLE IF EXISTS `namedqueries`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `namedqueries` ( + `id` mediumint(9) NOT NULL AUTO_INCREMENT, + `userid` mediumint(9) NOT NULL, + `name` varchar(64) NOT NULL, + `query` mediumtext NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `namedqueries_userid_idx` (`userid`,`name`), + CONSTRAINT `fk_namedqueries_userid_profiles_userid` FOREIGN KEY (`userid`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `namedqueries` +-- + +LOCK TABLES `namedqueries` WRITE; +/*!40000 ALTER TABLE `namedqueries` DISABLE KEYS */; +/*!40000 ALTER TABLE `namedqueries` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `namedqueries_link_in_footer` +-- + +DROP TABLE IF EXISTS `namedqueries_link_in_footer`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `namedqueries_link_in_footer` ( + `namedquery_id` mediumint(9) NOT NULL, + `user_id` mediumint(9) NOT NULL, + UNIQUE KEY `namedqueries_link_in_footer_id_idx` (`namedquery_id`,`user_id`), + KEY `namedqueries_link_in_footer_userid_idx` (`user_id`), + CONSTRAINT `fk_namedqueries_link_in_footer_namedquery_id_namedqueries_id` FOREIGN KEY (`namedquery_id`) REFERENCES `namedqueries` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_namedqueries_link_in_footer_user_id_profiles_userid` FOREIGN KEY (`user_id`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `namedqueries_link_in_footer` +-- + +LOCK TABLES `namedqueries_link_in_footer` WRITE; +/*!40000 ALTER TABLE `namedqueries_link_in_footer` DISABLE KEYS */; +/*!40000 ALTER TABLE `namedqueries_link_in_footer` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `namedquery_group_map` +-- + +DROP TABLE IF EXISTS `namedquery_group_map`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `namedquery_group_map` ( + `namedquery_id` mediumint(9) NOT NULL, + `group_id` mediumint(9) NOT NULL, + UNIQUE KEY `namedquery_group_map_namedquery_id_idx` (`namedquery_id`), + KEY `namedquery_group_map_group_id_idx` (`group_id`), + CONSTRAINT `fk_namedquery_group_map_group_id_groups_id` FOREIGN KEY (`group_id`) REFERENCES `groups` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_namedquery_group_map_namedquery_id_namedqueries_id` FOREIGN KEY (`namedquery_id`) REFERENCES `namedqueries` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `namedquery_group_map` +-- + +LOCK TABLES `namedquery_group_map` WRITE; +/*!40000 ALTER TABLE `namedquery_group_map` DISABLE KEYS */; +/*!40000 ALTER TABLE `namedquery_group_map` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `op_sys` +-- + +DROP TABLE IF EXISTS `op_sys`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `op_sys` ( + `id` smallint(6) NOT NULL AUTO_INCREMENT, + `value` varchar(64) NOT NULL, + `sortkey` smallint(6) NOT NULL DEFAULT 0, + `isactive` tinyint(4) NOT NULL DEFAULT 1, + `visibility_value_id` smallint(6) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `op_sys_value_idx` (`value`), + KEY `op_sys_sortkey_idx` (`sortkey`,`value`), + KEY `op_sys_visibility_value_id_idx` (`visibility_value_id`) +) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `op_sys` +-- + +LOCK TABLES `op_sys` WRITE; +/*!40000 ALTER TABLE `op_sys` DISABLE KEYS */; +INSERT INTO `op_sys` VALUES (1,'All',100,1,NULL),(2,'Windows',200,1,NULL),(3,'Mac OS',300,1,NULL),(4,'Linux',400,1,NULL),(5,'Other',500,1,NULL); +/*!40000 ALTER TABLE `op_sys` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `priority` +-- + +DROP TABLE IF EXISTS `priority`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `priority` ( + `id` smallint(6) NOT NULL AUTO_INCREMENT, + `value` varchar(64) NOT NULL, + `sortkey` smallint(6) NOT NULL DEFAULT 0, + `isactive` tinyint(4) NOT NULL DEFAULT 1, + `visibility_value_id` smallint(6) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `priority_value_idx` (`value`), + KEY `priority_sortkey_idx` (`sortkey`,`value`), + KEY `priority_visibility_value_id_idx` (`visibility_value_id`) +) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `priority` +-- + +LOCK TABLES `priority` WRITE; +/*!40000 ALTER TABLE `priority` DISABLE KEYS */; +INSERT INTO `priority` VALUES (1,'Highest',100,1,NULL),(2,'High',200,1,NULL),(3,'Normal',300,1,NULL),(4,'Low',400,1,NULL),(5,'Lowest',500,1,NULL),(6,'---',600,1,NULL); +/*!40000 ALTER TABLE `priority` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `products` +-- + +DROP TABLE IF EXISTS `products`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `products` ( + `id` smallint(6) NOT NULL AUTO_INCREMENT, + `name` varchar(64) NOT NULL, + `classification_id` smallint(6) NOT NULL DEFAULT 1, + `description` mediumtext NOT NULL, + `isactive` tinyint(4) NOT NULL DEFAULT 1, + `defaultmilestone` varchar(64) NOT NULL DEFAULT '---', + `allows_unconfirmed` tinyint(4) NOT NULL DEFAULT 1, + PRIMARY KEY (`id`), + UNIQUE KEY `products_name_idx` (`name`), + KEY `fk_products_classification_id_classifications_id` (`classification_id`), + CONSTRAINT `fk_products_classification_id_classifications_id` FOREIGN KEY (`classification_id`) REFERENCES `classifications` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `products` +-- + +LOCK TABLES `products` WRITE; +/*!40000 ALTER TABLE `products` DISABLE KEYS */; +INSERT INTO `products` VALUES (1,'TestProduct',1,'This is a test product. This ought to be blown away and replaced with real stuff in a finished installation of bugzilla.',1,'---',1),(2,'Red Hat Enterprise Linux 9',1,'Lorem ipsum',1,'---',1),(3,'SUSE Linux Enterprise Server 15 SP6',1,'Lorem ipsum dolor sit amet',1,'---',1); +/*!40000 ALTER TABLE `products` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `profile_search` +-- + +DROP TABLE IF EXISTS `profile_search`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `profile_search` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `user_id` mediumint(9) NOT NULL, + `bug_list` mediumtext NOT NULL, + `list_order` mediumtext DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `profile_search_user_id_idx` (`user_id`), + CONSTRAINT `fk_profile_search_user_id_profiles_userid` FOREIGN KEY (`user_id`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `profile_search` +-- + +LOCK TABLES `profile_search` WRITE; +/*!40000 ALTER TABLE `profile_search` DISABLE KEYS */; +INSERT INTO `profile_search` VALUES (1,1,'1','bug_status,priority,assigned_to,bug_id'),(2,1,'1,2','priority,bug_severity'),(3,1,'2','bug_status,priority,assigned_to,bug_id'); +/*!40000 ALTER TABLE `profile_search` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `profile_setting` +-- + +DROP TABLE IF EXISTS `profile_setting`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `profile_setting` ( + `user_id` mediumint(9) NOT NULL, + `setting_name` varchar(32) NOT NULL, + `setting_value` varchar(32) NOT NULL, + UNIQUE KEY `profile_setting_value_unique_idx` (`user_id`,`setting_name`), + KEY `fk_profile_setting_setting_name_setting_name` (`setting_name`), + CONSTRAINT `fk_profile_setting_setting_name_setting_name` FOREIGN KEY (`setting_name`) REFERENCES `setting` (`name`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_profile_setting_user_id_profiles_userid` FOREIGN KEY (`user_id`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `profile_setting` +-- + +LOCK TABLES `profile_setting` WRITE; +/*!40000 ALTER TABLE `profile_setting` DISABLE KEYS */; +/*!40000 ALTER TABLE `profile_setting` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `profiles` +-- + +DROP TABLE IF EXISTS `profiles`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `profiles` ( + `userid` mediumint(9) NOT NULL AUTO_INCREMENT, + `login_name` varchar(255) NOT NULL, + `cryptpassword` varchar(128) DEFAULT NULL, + `realname` varchar(255) NOT NULL DEFAULT '', + `disabledtext` mediumtext NOT NULL DEFAULT '', + `disable_mail` tinyint(4) NOT NULL DEFAULT 0, + `mybugslink` tinyint(4) NOT NULL DEFAULT 1, + `extern_id` varchar(64) DEFAULT NULL, + `is_enabled` tinyint(4) NOT NULL DEFAULT 1, + `last_seen_date` datetime DEFAULT NULL, + PRIMARY KEY (`userid`), + UNIQUE KEY `profiles_login_name_idx` (`login_name`), + UNIQUE KEY `profiles_extern_id_idx` (`extern_id`) +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `profiles` +-- + +LOCK TABLES `profiles` WRITE; +/*!40000 ALTER TABLE `profiles` DISABLE KEYS */; +INSERT INTO `profiles` VALUES (1,'andreas@hasenkopf.xyz','2207pp7o,ialUTtf7x78ge5SbbN7+W+1lXGJBXmMlYt26C1egd4g{SHA-256}','Andreas','',0,1,NULL,1,'2024-10-15 00:00:00'),(2,'nemo@example.com','rimPrF6O,Y0jPDDD1IeOR5myBbCCkt5rW36hOlVe7k/IH8wG513Y{SHA-256}','Nemo','',1,1,NULL,1,NULL); +/*!40000 ALTER TABLE `profiles` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `profiles_activity` +-- + +DROP TABLE IF EXISTS `profiles_activity`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `profiles_activity` ( + `id` mediumint(9) NOT NULL AUTO_INCREMENT, + `userid` mediumint(9) NOT NULL, + `who` mediumint(9) NOT NULL, + `profiles_when` datetime NOT NULL, + `fieldid` mediumint(9) NOT NULL, + `oldvalue` tinytext DEFAULT NULL, + `newvalue` tinytext DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `profiles_activity_userid_idx` (`userid`), + KEY `profiles_activity_profiles_when_idx` (`profiles_when`), + KEY `profiles_activity_fieldid_idx` (`fieldid`), + KEY `fk_profiles_activity_who_profiles_userid` (`who`), + CONSTRAINT `fk_profiles_activity_fieldid_fielddefs_id` FOREIGN KEY (`fieldid`) REFERENCES `fielddefs` (`id`) ON UPDATE CASCADE, + CONSTRAINT `fk_profiles_activity_userid_profiles_userid` FOREIGN KEY (`userid`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_profiles_activity_who_profiles_userid` FOREIGN KEY (`who`) REFERENCES `profiles` (`userid`) ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `profiles_activity` +-- + +LOCK TABLES `profiles_activity` WRITE; +/*!40000 ALTER TABLE `profiles_activity` DISABLE KEYS */; +INSERT INTO `profiles_activity` VALUES (1,1,1,'2023-09-20 13:12:55',33,NULL,'2023-09-20 13:12:55'),(2,2,1,'2024-10-15 13:28:58',33,NULL,'2024-10-15 13:28:58'); +/*!40000 ALTER TABLE `profiles_activity` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `quips` +-- + +DROP TABLE IF EXISTS `quips`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `quips` ( + `quipid` mediumint(9) NOT NULL AUTO_INCREMENT, + `userid` mediumint(9) DEFAULT NULL, + `quip` varchar(512) NOT NULL, + `approved` tinyint(4) NOT NULL DEFAULT 1, + PRIMARY KEY (`quipid`), + KEY `fk_quips_userid_profiles_userid` (`userid`), + CONSTRAINT `fk_quips_userid_profiles_userid` FOREIGN KEY (`userid`) REFERENCES `profiles` (`userid`) ON DELETE SET NULL ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `quips` +-- + +LOCK TABLES `quips` WRITE; +/*!40000 ALTER TABLE `quips` DISABLE KEYS */; +/*!40000 ALTER TABLE `quips` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `rep_platform` +-- + +DROP TABLE IF EXISTS `rep_platform`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `rep_platform` ( + `id` smallint(6) NOT NULL AUTO_INCREMENT, + `value` varchar(64) NOT NULL, + `sortkey` smallint(6) NOT NULL DEFAULT 0, + `isactive` tinyint(4) NOT NULL DEFAULT 1, + `visibility_value_id` smallint(6) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `rep_platform_value_idx` (`value`), + KEY `rep_platform_sortkey_idx` (`sortkey`,`value`), + KEY `rep_platform_visibility_value_id_idx` (`visibility_value_id`) +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `rep_platform` +-- + +LOCK TABLES `rep_platform` WRITE; +/*!40000 ALTER TABLE `rep_platform` DISABLE KEYS */; +INSERT INTO `rep_platform` VALUES (1,'All',100,1,NULL),(2,'PC',200,1,NULL),(3,'Macintosh',300,1,NULL),(4,'Other',400,1,NULL); +/*!40000 ALTER TABLE `rep_platform` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `reports` +-- + +DROP TABLE IF EXISTS `reports`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `reports` ( + `id` mediumint(9) NOT NULL AUTO_INCREMENT, + `user_id` mediumint(9) NOT NULL, + `name` varchar(64) NOT NULL, + `query` mediumtext NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `reports_user_id_idx` (`user_id`,`name`), + CONSTRAINT `fk_reports_user_id_profiles_userid` FOREIGN KEY (`user_id`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `reports` +-- + +LOCK TABLES `reports` WRITE; +/*!40000 ALTER TABLE `reports` DISABLE KEYS */; +/*!40000 ALTER TABLE `reports` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `resolution` +-- + +DROP TABLE IF EXISTS `resolution`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `resolution` ( + `id` smallint(6) NOT NULL AUTO_INCREMENT, + `value` varchar(64) NOT NULL, + `sortkey` smallint(6) NOT NULL DEFAULT 0, + `isactive` tinyint(4) NOT NULL DEFAULT 1, + `visibility_value_id` smallint(6) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `resolution_value_idx` (`value`), + KEY `resolution_sortkey_idx` (`sortkey`,`value`), + KEY `resolution_visibility_value_id_idx` (`visibility_value_id`) +) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `resolution` +-- + +LOCK TABLES `resolution` WRITE; +/*!40000 ALTER TABLE `resolution` DISABLE KEYS */; +INSERT INTO `resolution` VALUES (1,'',100,1,NULL),(2,'FIXED',200,1,NULL),(3,'INVALID',300,1,NULL),(4,'WONTFIX',400,1,NULL),(5,'DUPLICATE',500,1,NULL),(6,'WORKSFORME',600,1,NULL); +/*!40000 ALTER TABLE `resolution` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `series` +-- + +DROP TABLE IF EXISTS `series`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `series` ( + `series_id` mediumint(9) NOT NULL AUTO_INCREMENT, + `creator` mediumint(9) DEFAULT NULL, + `category` smallint(6) NOT NULL, + `subcategory` smallint(6) NOT NULL, + `name` varchar(64) NOT NULL, + `frequency` smallint(6) NOT NULL, + `query` mediumtext NOT NULL, + `is_public` tinyint(4) NOT NULL DEFAULT 0, + PRIMARY KEY (`series_id`), + UNIQUE KEY `series_category_idx` (`category`,`subcategory`,`name`), + KEY `series_creator_idx` (`creator`), + KEY `fk_series_subcategory_series_categories_id` (`subcategory`), + CONSTRAINT `fk_series_category_series_categories_id` FOREIGN KEY (`category`) REFERENCES `series_categories` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_series_creator_profiles_userid` FOREIGN KEY (`creator`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_series_subcategory_series_categories_id` FOREIGN KEY (`subcategory`) REFERENCES `series_categories` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `series` +-- + +LOCK TABLES `series` WRITE; +/*!40000 ALTER TABLE `series` DISABLE KEYS */; +INSERT INTO `series` VALUES (1,1,1,2,'UNCONFIRMED',1,'bug_status=UNCONFIRMED&product=Red%20Hat%20Enterprise%20Linux%209',1),(2,1,1,2,'CONFIRMED',1,'bug_status=CONFIRMED&product=Red%20Hat%20Enterprise%20Linux%209',1),(3,1,1,2,'IN_PROGRESS',1,'bug_status=IN_PROGRESS&product=Red%20Hat%20Enterprise%20Linux%209',1),(4,1,1,2,'RESOLVED',1,'bug_status=RESOLVED&product=Red%20Hat%20Enterprise%20Linux%209',1),(5,1,1,2,'VERIFIED',1,'bug_status=VERIFIED&product=Red%20Hat%20Enterprise%20Linux%209',1),(6,1,1,2,'FIXED',1,'resolution=FIXED&product=Red%20Hat%20Enterprise%20Linux%209',1),(7,1,1,2,'INVALID',1,'resolution=INVALID&product=Red%20Hat%20Enterprise%20Linux%209',1),(8,1,1,2,'WONTFIX',1,'resolution=WONTFIX&product=Red%20Hat%20Enterprise%20Linux%209',1),(9,1,1,2,'DUPLICATE',1,'resolution=DUPLICATE&product=Red%20Hat%20Enterprise%20Linux%209',1),(10,1,1,2,'WORKSFORME',1,'resolution=WORKSFORME&product=Red%20Hat%20Enterprise%20Linux%209',1),(11,1,1,2,'All Open',1,'bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=IN_PROGRESS&product=Red%20Hat%20Enterprise%20Linux%209',1),(12,1,1,3,'All Open',1,'field0-0-0=resolution&type0-0-0=notregexp&value0-0-0=.&product=Red%20Hat%20Enterprise%20Linux%209&component=python-bugzilla',1),(13,1,1,3,'All Closed',1,'field0-0-0=resolution&type0-0-0=regexp&value0-0-0=.&product=Red%20Hat%20Enterprise%20Linux%209&component=python-bugzilla',1),(14,1,4,2,'UNCONFIRMED',1,'bug_status=UNCONFIRMED&product=SUSE%20Linux%20Enterprise%20Server%2015%20SP6',1),(15,1,4,2,'CONFIRMED',1,'bug_status=CONFIRMED&product=SUSE%20Linux%20Enterprise%20Server%2015%20SP6',1),(16,1,4,2,'IN_PROGRESS',1,'bug_status=IN_PROGRESS&product=SUSE%20Linux%20Enterprise%20Server%2015%20SP6',1),(17,1,4,2,'RESOLVED',1,'bug_status=RESOLVED&product=SUSE%20Linux%20Enterprise%20Server%2015%20SP6',1),(18,1,4,2,'VERIFIED',1,'bug_status=VERIFIED&product=SUSE%20Linux%20Enterprise%20Server%2015%20SP6',1),(19,1,4,2,'FIXED',1,'resolution=FIXED&product=SUSE%20Linux%20Enterprise%20Server%2015%20SP6',1),(20,1,4,2,'INVALID',1,'resolution=INVALID&product=SUSE%20Linux%20Enterprise%20Server%2015%20SP6',1),(21,1,4,2,'WONTFIX',1,'resolution=WONTFIX&product=SUSE%20Linux%20Enterprise%20Server%2015%20SP6',1),(22,1,4,2,'DUPLICATE',1,'resolution=DUPLICATE&product=SUSE%20Linux%20Enterprise%20Server%2015%20SP6',1),(23,1,4,2,'WORKSFORME',1,'resolution=WORKSFORME&product=SUSE%20Linux%20Enterprise%20Server%2015%20SP6',1),(24,1,4,2,'All Open',1,'bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=IN_PROGRESS&product=SUSE%20Linux%20Enterprise%20Server%2015%20SP6',1),(25,1,4,5,'All Open',1,'field0-0-0=resolution&type0-0-0=notregexp&value0-0-0=.&product=SUSE%20Linux%20Enterprise%20Server%2015%20SP6&component=Kernel',1),(26,1,4,5,'All Closed',1,'field0-0-0=resolution&type0-0-0=regexp&value0-0-0=.&product=SUSE%20Linux%20Enterprise%20Server%2015%20SP6&component=Kernel',1),(27,1,4,6,'All Open',1,'field0-0-0=resolution&type0-0-0=notregexp&value0-0-0=.&product=SUSE%20Linux%20Enterprise%20Server%2015%20SP6&component=Containers',1),(28,1,4,6,'All Closed',1,'field0-0-0=resolution&type0-0-0=regexp&value0-0-0=.&product=SUSE%20Linux%20Enterprise%20Server%2015%20SP6&component=Containers',1); +/*!40000 ALTER TABLE `series` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `series_categories` +-- + +DROP TABLE IF EXISTS `series_categories`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `series_categories` ( + `id` smallint(6) NOT NULL AUTO_INCREMENT, + `name` varchar(64) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `series_categories_name_idx` (`name`) +) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `series_categories` +-- + +LOCK TABLES `series_categories` WRITE; +/*!40000 ALTER TABLE `series_categories` DISABLE KEYS */; +INSERT INTO `series_categories` VALUES (2,'-All-'),(6,'Containers'),(5,'Kernel'),(3,'python-bugzilla'),(1,'Red Hat Enterprise Linux 9'),(4,'SUSE Linux Enterprise Server 15 SP6'); +/*!40000 ALTER TABLE `series_categories` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `series_data` +-- + +DROP TABLE IF EXISTS `series_data`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `series_data` ( + `series_id` mediumint(9) NOT NULL, + `series_date` datetime NOT NULL, + `series_value` mediumint(9) NOT NULL, + UNIQUE KEY `series_data_series_id_idx` (`series_id`,`series_date`), + CONSTRAINT `fk_series_data_series_id_series_series_id` FOREIGN KEY (`series_id`) REFERENCES `series` (`series_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `series_data` +-- + +LOCK TABLES `series_data` WRITE; +/*!40000 ALTER TABLE `series_data` DISABLE KEYS */; +/*!40000 ALTER TABLE `series_data` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `setting` +-- + +DROP TABLE IF EXISTS `setting`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `setting` ( + `name` varchar(32) NOT NULL, + `default_value` varchar(32) NOT NULL, + `is_enabled` tinyint(4) NOT NULL DEFAULT 1, + `subclass` varchar(32) DEFAULT NULL, + PRIMARY KEY (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `setting` +-- + +LOCK TABLES `setting` WRITE; +/*!40000 ALTER TABLE `setting` DISABLE KEYS */; +INSERT INTO `setting` VALUES ('bugmail_new_prefix','on',1,NULL),('comment_box_position','before_comments',1,NULL),('comment_sort_order','oldest_to_newest',1,NULL),('csv_colsepchar',',',1,NULL),('display_quips','on',1,NULL),('email_format','html',1,NULL),('lang','en',1,'Lang'),('possible_duplicates','on',1,NULL),('post_bug_submit_action','next_bug',1,NULL),('quicksearch_fulltext','on',1,NULL),('quote_replies','quoted_reply',1,NULL),('requestee_cc','on',1,NULL),('skin','Dusk',1,'Skin'),('state_addselfcc','cc_unless_role',1,NULL),('timezone','local',1,'Timezone'),('zoom_textareas','on',1,NULL); +/*!40000 ALTER TABLE `setting` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `setting_value` +-- + +DROP TABLE IF EXISTS `setting_value`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `setting_value` ( + `name` varchar(32) NOT NULL, + `value` varchar(32) NOT NULL, + `sortindex` smallint(6) NOT NULL, + UNIQUE KEY `setting_value_nv_unique_idx` (`name`,`value`), + UNIQUE KEY `setting_value_ns_unique_idx` (`name`,`sortindex`), + CONSTRAINT `fk_setting_value_name_setting_name` FOREIGN KEY (`name`) REFERENCES `setting` (`name`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `setting_value` +-- + +LOCK TABLES `setting_value` WRITE; +/*!40000 ALTER TABLE `setting_value` DISABLE KEYS */; +INSERT INTO `setting_value` VALUES ('bugmail_new_prefix','on',5),('bugmail_new_prefix','off',10),('comment_box_position','before_comments',5),('comment_box_position','after_comments',10),('comment_sort_order','oldest_to_newest',5),('comment_sort_order','newest_to_oldest',10),('comment_sort_order','newest_to_oldest_desc_first',15),('csv_colsepchar',',',5),('csv_colsepchar',';',10),('display_quips','on',5),('display_quips','off',10),('email_format','html',5),('email_format','text_only',10),('possible_duplicates','on',5),('possible_duplicates','off',10),('post_bug_submit_action','next_bug',5),('post_bug_submit_action','same_bug',10),('post_bug_submit_action','nothing',15),('quicksearch_fulltext','on',5),('quicksearch_fulltext','off',10),('quote_replies','quoted_reply',5),('quote_replies','simple_reply',10),('quote_replies','off',15),('requestee_cc','on',5),('requestee_cc','off',10),('state_addselfcc','always',5),('state_addselfcc','never',10),('state_addselfcc','cc_unless_role',15),('zoom_textareas','on',5),('zoom_textareas','off',10); +/*!40000 ALTER TABLE `setting_value` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `status_workflow` +-- + +DROP TABLE IF EXISTS `status_workflow`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `status_workflow` ( + `old_status` smallint(6) DEFAULT NULL, + `new_status` smallint(6) NOT NULL, + `require_comment` tinyint(4) NOT NULL DEFAULT 0, + UNIQUE KEY `status_workflow_idx` (`old_status`,`new_status`), + KEY `fk_status_workflow_new_status_bug_status_id` (`new_status`), + CONSTRAINT `fk_status_workflow_new_status_bug_status_id` FOREIGN KEY (`new_status`) REFERENCES `bug_status` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_status_workflow_old_status_bug_status_id` FOREIGN KEY (`old_status`) REFERENCES `bug_status` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `status_workflow` +-- + +LOCK TABLES `status_workflow` WRITE; +/*!40000 ALTER TABLE `status_workflow` DISABLE KEYS */; +INSERT INTO `status_workflow` VALUES (NULL,1,0),(NULL,2,0),(NULL,3,0),(1,2,0),(1,3,0),(1,4,0),(2,3,0),(2,4,0),(3,2,0),(3,4,0),(4,1,0),(4,2,0),(4,5,0),(5,1,0),(5,2,0); +/*!40000 ALTER TABLE `status_workflow` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `tag` +-- + +DROP TABLE IF EXISTS `tag`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `tag` ( + `id` mediumint(9) NOT NULL AUTO_INCREMENT, + `name` varchar(64) NOT NULL, + `user_id` mediumint(9) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `tag_user_id_idx` (`user_id`,`name`), + CONSTRAINT `fk_tag_user_id_profiles_userid` FOREIGN KEY (`user_id`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `tag` +-- + +LOCK TABLES `tag` WRITE; +/*!40000 ALTER TABLE `tag` DISABLE KEYS */; +/*!40000 ALTER TABLE `tag` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `tokens` +-- + +DROP TABLE IF EXISTS `tokens`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `tokens` ( + `userid` mediumint(9) DEFAULT NULL, + `issuedate` datetime NOT NULL, + `token` varchar(16) NOT NULL, + `tokentype` varchar(16) NOT NULL, + `eventdata` tinytext DEFAULT NULL, + PRIMARY KEY (`token`), + KEY `tokens_userid_idx` (`userid`), + CONSTRAINT `fk_tokens_userid_profiles_userid` FOREIGN KEY (`userid`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `tokens` +-- + +LOCK TABLES `tokens` WRITE; +/*!40000 ALTER TABLE `tokens` DISABLE KEYS */; +INSERT INTO `tokens` VALUES (1,'2023-11-27 15:46:15','5HVJhRRo6t','session','edit_parameters'),(1,'2024-10-15 13:06:14','5NG9DysR5W','session','edit_parameters'),(1,'2024-10-15 13:10:16','6m73C0nqfo','session','edit_parameters'),(1,'2024-10-15 13:10:09','7RlXVAQiOb','session','edit_parameters'),(1,'2023-11-27 12:25:54','a9MgwT7N7x','session','edit_product'),(1,'2024-10-15 13:27:02','bSVcXqgap4','session','edit_flagtype'),(1,'2024-10-15 13:06:09','BWsu8P8e2D','session','edit_parameters'),(1,'2023-11-27 15:42:50','CRSwDhzaXc','session','edit_parameters'),(1,'2024-10-15 14:02:08','dAVlRMDOg7','session','edit_component'),(1,'2023-11-27 12:29:18','DXFuAIZ5GH','session','edit_product'),(1,'2023-09-20 13:13:14','ery9F3ZaAV','session','edit_user_prefs'),(1,'2024-10-15 12:46:48','gEsxMu9BHz','api_token',''),(1,'2023-11-27 15:44:26','gnPazrbni2','session','edit_product'),(1,'2023-11-27 15:43:10','GZT1mYgIAF','session','edit_settings'),(1,'2023-11-27 15:42:57','hYkjAGXNIj','session','add_field'),(1,'2024-10-15 13:15:12','I9aiLWHFRJ','session','workflow_edit'),(1,'2023-11-27 15:46:35','ibDe8MPzGE','session','edit_parameters'),(1,'2024-10-15 14:00:21','ITqzn9Ed9n','session','edit_product'),(1,'2024-10-15 13:06:33','jK4PGdugR8','session','edit_parameters'),(1,'2024-10-15 14:02:05','JOhZj5gVqg','session','edit_product'),(1,'2023-09-20 13:13:14','oukIJJwYod','api_token',''),(1,'2023-11-27 12:26:29','PIjhZLJ29K','session','edit_product'),(1,'2023-11-27 12:23:39','pIrqNpsRDo','api_token',''),(1,'2024-10-15 13:28:58','qO1ZPdshDu','session','edit_user'),(1,'2023-11-27 15:44:36','rkyOtDBxr4','session','edit_group_controls'),(1,'2023-09-20 13:13:20','VLrgLovfH9','session','edit_user_prefs'),(1,'2024-10-15 13:10:07','w7KWafB5zu','session','edit_parameters'),(1,'2023-11-27 15:45:59','xgQpxIS10M','session','edit_user_prefs'),(1,'2024-10-15 14:02:53','YnDsGT0jbR','session','add_component'); +/*!40000 ALTER TABLE `tokens` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `ts_error` +-- + +DROP TABLE IF EXISTS `ts_error`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `ts_error` ( + `error_time` int(11) NOT NULL, + `jobid` int(11) NOT NULL, + `message` varchar(255) NOT NULL, + `funcid` int(11) NOT NULL DEFAULT 0, + KEY `ts_error_funcid_idx` (`funcid`,`error_time`), + KEY `ts_error_error_time_idx` (`error_time`), + KEY `ts_error_jobid_idx` (`jobid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `ts_error` +-- + +LOCK TABLES `ts_error` WRITE; +/*!40000 ALTER TABLE `ts_error` DISABLE KEYS */; +/*!40000 ALTER TABLE `ts_error` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `ts_exitstatus` +-- + +DROP TABLE IF EXISTS `ts_exitstatus`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `ts_exitstatus` ( + `jobid` int(11) NOT NULL AUTO_INCREMENT, + `funcid` int(11) NOT NULL DEFAULT 0, + `status` smallint(6) DEFAULT NULL, + `completion_time` int(11) DEFAULT NULL, + `delete_after` int(11) DEFAULT NULL, + PRIMARY KEY (`jobid`), + KEY `ts_exitstatus_funcid_idx` (`funcid`), + KEY `ts_exitstatus_delete_after_idx` (`delete_after`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `ts_exitstatus` +-- + +LOCK TABLES `ts_exitstatus` WRITE; +/*!40000 ALTER TABLE `ts_exitstatus` DISABLE KEYS */; +/*!40000 ALTER TABLE `ts_exitstatus` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `ts_funcmap` +-- + +DROP TABLE IF EXISTS `ts_funcmap`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `ts_funcmap` ( + `funcid` int(11) NOT NULL AUTO_INCREMENT, + `funcname` varchar(255) NOT NULL, + PRIMARY KEY (`funcid`), + UNIQUE KEY `ts_funcmap_funcname_idx` (`funcname`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `ts_funcmap` +-- + +LOCK TABLES `ts_funcmap` WRITE; +/*!40000 ALTER TABLE `ts_funcmap` DISABLE KEYS */; +/*!40000 ALTER TABLE `ts_funcmap` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `ts_job` +-- + +DROP TABLE IF EXISTS `ts_job`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `ts_job` ( + `jobid` int(11) NOT NULL AUTO_INCREMENT, + `funcid` int(11) NOT NULL, + `arg` longblob DEFAULT NULL, + `uniqkey` varchar(255) DEFAULT NULL, + `insert_time` int(11) DEFAULT NULL, + `run_after` int(11) NOT NULL, + `grabbed_until` int(11) NOT NULL, + `priority` smallint(6) DEFAULT NULL, + `coalesce` varchar(255) DEFAULT NULL, + PRIMARY KEY (`jobid`), + UNIQUE KEY `ts_job_funcid_idx` (`funcid`,`uniqkey`), + KEY `ts_job_run_after_idx` (`run_after`,`funcid`), + KEY `ts_job_coalesce_idx` (`coalesce`,`funcid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `ts_job` +-- + +LOCK TABLES `ts_job` WRITE; +/*!40000 ALTER TABLE `ts_job` DISABLE KEYS */; +/*!40000 ALTER TABLE `ts_job` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `ts_note` +-- + +DROP TABLE IF EXISTS `ts_note`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `ts_note` ( + `jobid` int(11) NOT NULL, + `notekey` varchar(255) DEFAULT NULL, + `value` longblob DEFAULT NULL, + UNIQUE KEY `ts_note_jobid_idx` (`jobid`,`notekey`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `ts_note` +-- + +LOCK TABLES `ts_note` WRITE; +/*!40000 ALTER TABLE `ts_note` DISABLE KEYS */; +/*!40000 ALTER TABLE `ts_note` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `user_api_keys` +-- + +DROP TABLE IF EXISTS `user_api_keys`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `user_api_keys` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `user_id` mediumint(9) NOT NULL, + `api_key` varchar(40) NOT NULL, + `description` varchar(255) DEFAULT NULL, + `revoked` tinyint(4) NOT NULL DEFAULT 0, + `last_used` datetime DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `user_api_keys_api_key_idx` (`api_key`), + KEY `user_api_keys_user_id_idx` (`user_id`), + CONSTRAINT `fk_user_api_keys_user_id_profiles_userid` FOREIGN KEY (`user_id`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `user_api_keys` +-- + +LOCK TABLES `user_api_keys` WRITE; +/*!40000 ALTER TABLE `user_api_keys` DISABLE KEYS */; +INSERT INTO `user_api_keys` VALUES (1,1,'AxBntHGSL97CmoTahkey8RNyo2K65NEfJBuk5ATe','',0,NULL); +/*!40000 ALTER TABLE `user_api_keys` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `user_group_map` +-- + +DROP TABLE IF EXISTS `user_group_map`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `user_group_map` ( + `user_id` mediumint(9) NOT NULL, + `group_id` mediumint(9) NOT NULL, + `isbless` tinyint(4) NOT NULL DEFAULT 0, + `grant_type` tinyint(4) NOT NULL DEFAULT 0, + UNIQUE KEY `user_group_map_user_id_idx` (`user_id`,`group_id`,`grant_type`,`isbless`), + KEY `fk_user_group_map_group_id_groups_id` (`group_id`), + CONSTRAINT `fk_user_group_map_group_id_groups_id` FOREIGN KEY (`group_id`) REFERENCES `groups` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_user_group_map_user_id_profiles_userid` FOREIGN KEY (`user_id`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `user_group_map` +-- + +LOCK TABLES `user_group_map` WRITE; +/*!40000 ALTER TABLE `user_group_map` DISABLE KEYS */; +INSERT INTO `user_group_map` VALUES (1,1,0,0),(1,1,1,0),(1,3,0,0),(1,8,0,2),(2,8,0,2); +/*!40000 ALTER TABLE `user_group_map` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `versions` +-- + +DROP TABLE IF EXISTS `versions`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `versions` ( + `id` mediumint(9) NOT NULL AUTO_INCREMENT, + `value` varchar(64) NOT NULL, + `product_id` smallint(6) NOT NULL, + `isactive` tinyint(4) NOT NULL DEFAULT 1, + PRIMARY KEY (`id`), + UNIQUE KEY `versions_product_id_idx` (`product_id`,`value`), + CONSTRAINT `fk_versions_product_id_products_id` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `versions` +-- + +LOCK TABLES `versions` WRITE; +/*!40000 ALTER TABLE `versions` DISABLE KEYS */; +INSERT INTO `versions` VALUES (1,'unspecified',1,1),(2,'unspecified',2,1),(3,'9.0',2,1),(4,'9.1',2,1),(5,'unspecified',3,1); +/*!40000 ALTER TABLE `versions` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `watch` +-- + +DROP TABLE IF EXISTS `watch`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `watch` ( + `watcher` mediumint(9) NOT NULL, + `watched` mediumint(9) NOT NULL, + UNIQUE KEY `watch_watcher_idx` (`watcher`,`watched`), + KEY `watch_watched_idx` (`watched`), + CONSTRAINT `fk_watch_watched_profiles_userid` FOREIGN KEY (`watched`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_watch_watcher_profiles_userid` FOREIGN KEY (`watcher`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `watch` +-- + +LOCK TABLES `watch` WRITE; +/*!40000 ALTER TABLE `watch` DISABLE KEYS */; +/*!40000 ALTER TABLE `watch` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `whine_events` +-- + +DROP TABLE IF EXISTS `whine_events`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `whine_events` ( + `id` mediumint(9) NOT NULL AUTO_INCREMENT, + `owner_userid` mediumint(9) NOT NULL, + `subject` varchar(128) DEFAULT NULL, + `body` mediumtext DEFAULT NULL, + `mailifnobugs` tinyint(4) NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + KEY `fk_whine_events_owner_userid_profiles_userid` (`owner_userid`), + CONSTRAINT `fk_whine_events_owner_userid_profiles_userid` FOREIGN KEY (`owner_userid`) REFERENCES `profiles` (`userid`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `whine_events` +-- + +LOCK TABLES `whine_events` WRITE; +/*!40000 ALTER TABLE `whine_events` DISABLE KEYS */; +/*!40000 ALTER TABLE `whine_events` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `whine_queries` +-- + +DROP TABLE IF EXISTS `whine_queries`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `whine_queries` ( + `id` mediumint(9) NOT NULL AUTO_INCREMENT, + `eventid` mediumint(9) NOT NULL, + `query_name` varchar(64) NOT NULL DEFAULT '', + `sortkey` smallint(6) NOT NULL DEFAULT 0, + `onemailperbug` tinyint(4) NOT NULL DEFAULT 0, + `title` varchar(128) NOT NULL DEFAULT '', + PRIMARY KEY (`id`), + KEY `whine_queries_eventid_idx` (`eventid`), + CONSTRAINT `fk_whine_queries_eventid_whine_events_id` FOREIGN KEY (`eventid`) REFERENCES `whine_events` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `whine_queries` +-- + +LOCK TABLES `whine_queries` WRITE; +/*!40000 ALTER TABLE `whine_queries` DISABLE KEYS */; +/*!40000 ALTER TABLE `whine_queries` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `whine_schedules` +-- + +DROP TABLE IF EXISTS `whine_schedules`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `whine_schedules` ( + `id` mediumint(9) NOT NULL AUTO_INCREMENT, + `eventid` mediumint(9) NOT NULL, + `run_day` varchar(32) DEFAULT NULL, + `run_time` varchar(32) DEFAULT NULL, + `run_next` datetime DEFAULT NULL, + `mailto` mediumint(9) NOT NULL, + `mailto_type` smallint(6) NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + KEY `whine_schedules_run_next_idx` (`run_next`), + KEY `whine_schedules_eventid_idx` (`eventid`), + CONSTRAINT `fk_whine_schedules_eventid_whine_events_id` FOREIGN KEY (`eventid`) REFERENCES `whine_events` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `whine_schedules` +-- + +LOCK TABLES `whine_schedules` WRITE; +/*!40000 ALTER TABLE `whine_schedules` DISABLE KEYS */; +/*!40000 ALTER TABLE `whine_schedules` ENABLE KEYS */; +UNLOCK TABLES; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2024-10-15 16:07:04 diff --git a/tests/services/bugzilla.conf b/tests/services/bugzilla.conf new file mode 100644 index 00000000..c0de1250 --- /dev/null +++ b/tests/services/bugzilla.conf @@ -0,0 +1,9 @@ + + DocumentRoot /var/www/webapps/bugzilla + + AddHandler cgi-script .cgi + Options +ExecCGI + DirectoryIndex index.cgi index.html + AllowOverride All + + diff --git a/tests/services/bugzillarc b/tests/services/bugzillarc new file mode 100644 index 00000000..7f6dafaa --- /dev/null +++ b/tests/services/bugzillarc @@ -0,0 +1,2 @@ +[localhost] +api_key = AxBntHGSL97CmoTahkey8RNyo2K65NEfJBuk5ATe diff --git a/tests/services/localconfig b/tests/services/localconfig new file mode 100644 index 00000000..f3bddb99 --- /dev/null +++ b/tests/services/localconfig @@ -0,0 +1,19 @@ +$create_htaccess = 1; +$webservergroup = 'www-data'; +$use_suexec = 0; +$db_driver = 'mysql'; +$db_host = 'mariadb'; +$db_name = 'bugs'; +$db_user = 'bugs'; +$db_pass = 'secret'; +$db_port = 3306; +$db_sock = ''; +$db_check = 1; +$db_mysql_ssl_ca_file = ''; +$db_mysql_ssl_ca_path = ''; +$db_mysql_ssl_client_cert = ''; +$db_mysql_ssl_client_key = ''; +$index_html = 0; +$interdiffbin = ''; +$diffpath = '/usr/bin'; +$site_wide_secret = 'oCIbi5WC04h86lW7L8fDcPCrVjb3JNeA2St94QlQtfjZrorjKmOdeVV0feHNDeFH'; diff --git a/tests/services/params.json b/tests/services/params.json new file mode 100644 index 00000000..9a6a9034 --- /dev/null +++ b/tests/services/params.json @@ -0,0 +1,104 @@ +{ + "LDAPBaseDN" : "", + "LDAPbinddn" : "", + "LDAPfilter" : "", + "LDAPmailattribute" : "mail", + "LDAPserver" : "", + "LDAPstarttls" : "0", + "LDAPuidattribute" : "uid", + "RADIUS_NAS_IP" : "", + "RADIUS_email_suffix" : "", + "RADIUS_secret" : "", + "RADIUS_server" : "", + "ajax_user_autocompletion" : "1", + "allow_attachment_deletion" : "0", + "allow_attachment_display" : "0", + "allowbugdeletion" : "0", + "allowemailchange" : "1", + "allowuserdeletion" : "0", + "announcehtml" : "", + "attachment_base" : "", + "auth_env_email" : "", + "auth_env_id" : "", + "auth_env_realname" : "", + "chartgroup" : "editbugs", + "collapsed_comment_tags" : "obsolete, spam", + "comment_taggers_group" : "editbugs", + "commentonchange_resolution" : "0", + "commentonduplicate" : "0", + "confirmuniqueusermatch" : "1", + "cookiedomain" : "", + "cookiepath" : "/", + "createemailregexp" : ".*", + "debug_group" : "admin", + "default_search_limit" : "500", + "defaultopsys" : "", + "defaultplatform" : "", + "defaultpriority" : "---", + "defaultquery" : "resolution=---&emailassigned_to1=1&emailassigned_to2=1&emailreporter2=1&emailcc2=1&emailqa_contact2=1&emaillongdesc3=1&order=Importance&long_desc_type=substring", + "defaultseverity" : "enhancement", + "duplicate_or_move_bug_status" : "RESOLVED", + "emailregexp" : "^[\\w\\.\\+\\-=']+@[\\w\\.\\-]+\\.[\\w\\-]+$", + "emailregexpdesc" : "A legal address must contain exactly one '@', and at least one '.' after the @.", + "emailsuffix" : "", + "font_file" : "", + "globalwatchers" : "", + "inbound_proxies" : "", + "insidergroup" : "editbugs", + "last_visit_keep_days" : "10", + "letsubmitterchoosemilestone" : "1", + "letsubmitterchoosepriority" : "1", + "mail_delivery_method" : "None", + "mailfrom" : "bugzilla-daemon", + "maintainer" : "andreas@hasenkopf.xyz", + "makeproductgroups" : "0", + "max_search_results" : "10000", + "maxattachmentsize" : "1000", + "maxlocalattachment" : "0", + "maxusermatches" : "1000", + "memcached_namespace" : "bugzilla:", + "memcached_servers" : "", + "musthavemilestoneonaccept" : "0", + "mybugstemplate" : "buglist.cgi?resolution=---&emailassigned_to1=1&emailreporter1=1&emailtype1=exact&email1=%userid%", + "noresolveonopenblockers" : "0", + "or_groups" : "1", + "password_check_on_login" : "1", + "password_complexity" : "no_constraints", + "proxy_url" : "", + "querysharegroup" : "editbugs", + "quip_list_entry_control" : "open", + "rememberlogin" : "on", + "requirelogin" : "0", + "search_allow_no_criteria" : "0", + "shadowdb" : "", + "shadowdbhost" : "", + "shadowdbport" : "3306", + "shadowdbsock" : "", + "shutdownhtml" : "", + "smtp_debug" : "0", + "smtp_password" : "", + "smtp_ssl" : "0", + "smtp_username" : "", + "smtpserver" : "localhost", + "ssl_redirect" : "0", + "sslbase" : "", + "strict_isolation" : "0", + "strict_transport_security" : "off", + "timetrackinggroup" : "editbugs", + "upgrade_notification" : "latest_stable_release", + "urlbase" : "", + "use_mailer_queue" : "0", + "use_see_also" : "1", + "useclassification" : "0", + "usemenuforusers" : "0", + "useqacontact" : "0", + "user_info_class" : "CGI", + "user_verify_class" : "DB", + "usestatuswhiteboard" : "0", + "usetargetmilestone" : "0", + "usevisibilitygroups" : "0", + "utf8" : "1", + "webdotbase" : "", + "webservice_email_filter" : "0", + "whinedays" : "7" +} diff --git a/tests/test_api_authfiles.py b/tests/test_api_authfiles.py index 222a9ea2..fcd6fbd8 100644 --- a/tests/test_api_authfiles.py +++ b/tests/test_api_authfiles.py @@ -13,45 +13,20 @@ import shutil import tempfile -import pytest -import requests - -import bugzilla - import tests import tests.mockbackend import tests.utils -def testCookies(): +def test_tokenfile(monkeypatch): dirname = os.path.dirname(__file__) - cookiesbad = dirname + "/data/cookies-bad.txt" - cookieslwp = dirname + "/data/cookies-lwp.txt" - cookiesmoz = dirname + "/data/cookies-moz.txt" - - # We used to convert LWP cookies, but it shouldn't matter anymore, - # so verify they fail at least - with pytest.raises(bugzilla.BugzillaError): - tests.mockbackend.make_bz(version="3.0.0", - bz_kwargs={"cookiefile": cookieslwp, "use_creds": True}) + monkeypatch.setitem(os.environ, "HOME", dirname + "/data/homedir") - with pytest.raises(bugzilla.BugzillaError): - tests.mockbackend.make_bz(version="3.0.0", - bz_kwargs={"cookiefile": cookiesbad, "use_creds": True}) - - # Mozilla should 'just work' - bz = tests.mockbackend.make_bz(version="3.0.0", - bz_kwargs={"cookiefile": cookiesmoz, "use_creds": True}) - - # cookie/token property magic bz = tests.mockbackend.make_bz(bz_kwargs={"use_creds": True}) token = dirname + "/data/homedir/.cache/python-bugzilla/bugzillatoken" - cookie = dirname + "/data/homedir/.cache/python-bugzilla/bugzillacookies" assert token == bz.tokenfile - assert cookie == bz.cookiefile - del(bz.tokenfile) - del(bz.cookiefile) + del bz.tokenfile assert bz.tokenfile is None assert bz.cookiefile is None @@ -121,20 +96,12 @@ def _write(c): # Test confipath overwrite assert [temp.name] == bzapi.configpath - del(bzapi.configpath) + del bzapi.configpath assert [] == bzapi.configpath bzapi.readconfig() _check(None, None, None, None) -def _get_cookiejar(): - cookiefile = os.path.dirname(__file__) + "/data/cookies-moz.txt" - inputbz = tests.mockbackend.make_bz( - bz_kwargs={"use_creds": True, "cookiefile": cookiefile}) - cookiecache = inputbz._cookiecache # pylint: disable=protected-access - return cookiecache.get_cookiejar() - - def test_authfiles_saving(monkeypatch): tmpdir = tempfile.mkdtemp() try: @@ -147,41 +114,27 @@ def test_authfiles_saving(monkeypatch): bzapi.cert = "foo-fake-path" backend = bzapi._backend # pylint: disable=protected-access bsession = backend._bugzillasession # pylint: disable=protected-access - - response = requests.Response() - response.cookies = _get_cookiejar() + btokencache = bzapi._tokencache # pylint: disable=protected-access # token testing, with repetitions to hit various code paths - bsession.set_token_value(None) - bsession.set_token_value("MY-FAKE-TOKEN") - bsession.set_token_value("MY-FAKE-TOKEN") - bsession.set_token_value(None) - bsession.set_token_value("MY-FAKE-TOKEN") - - # cookie testing - bsession.set_response_cookies(response) + btokencache.set_value(bzapi.url, None) + assert "Bugzilla_token" not in bsession.get_auth_params() + btokencache.set_value(bzapi.url, "MY-FAKE-TOKEN") + assert bsession.get_auth_params()["Bugzilla_token"] == "MY-FAKE-TOKEN" + btokencache.set_value(bzapi.url, "MY-FAKE-TOKEN") + btokencache.set_value(bzapi.url, None) + assert "Bugzilla_token" not in bsession.get_auth_params() + btokencache.set_value(bzapi.url, "MY-FAKE-TOKEN") dirname = os.path.dirname(__file__) + "/data/authfiles/" output_token = dirname + "output-token.txt" - output_cookies = dirname + "output-cookies.txt" tests.utils.diff_compare(open(bzapi.tokenfile).read(), output_token) - # On RHEL7 the cookie comment header is different. Strip off leading - # comments - def strip_comments(f): - return "".join([l for l in open(f).readlines() if - not l.startswith("#")]) - - tests.utils.diff_compare(strip_comments(bzapi.cookiefile), - None, expect_out=strip_comments(output_cookies)) - # Make sure file can re-read them and not error bzapi = tests.mockbackend.make_bz( bz_kwargs={"use_creds": True, - "cookiefile": output_cookies, "tokenfile": output_token}) assert bzapi.tokenfile == output_token - assert bzapi.cookiefile == output_cookies # Test rcfile writing for api_key rcfile = bzapi._rcfile # pylint: disable=protected-access @@ -201,16 +154,11 @@ def strip_comments(f): def test_authfiles_nowrite(): - # Set values when n when cookiefile is None, should be fine + # Setting values tokenfile is None, should be fine bzapi = tests.mockbackend.make_bz(bz_kwargs={"use_creds": False}) bzapi.connect("https://example.com/foo") - backend = bzapi._backend # pylint: disable=protected-access - bsession = backend._bugzillasession # pylint: disable=protected-access + btokencache = bzapi._tokencache # pylint: disable=protected-access rcfile = bzapi._rcfile # pylint: disable=protected-access - response = requests.Response() - response.cookies = _get_cookiejar() - - bsession.set_token_value("NEW-TOKEN-VALUE") - bsession.set_response_cookies(response) + btokencache.set_value(bzapi.url, "NEW-TOKEN-VALUE") assert rcfile.save_api_key(bzapi.url, "fookey") is None diff --git a/tests/test_api_bug.py b/tests/test_api_bug.py index 54372269..23448d6b 100644 --- a/tests/test_api_bug.py +++ b/tests/test_api_bug.py @@ -9,8 +9,8 @@ Unit tests for testing some bug.py magic """ +import io import pickle -import sys import pytest @@ -59,13 +59,7 @@ def _assert_bug(): dir(bug) # Test special pickle support - if sys.version_info[0] >= 3: - import io - fd = io.BytesIO() - else: - import StringIO # pylint: disable=import-error - fd = StringIO.StringIO() - + fd = io.BytesIO() pickle.dump(bug, fd) fd.seek(0) bug = pickle.load(fd) @@ -100,6 +94,29 @@ def test_api_getbugs(): assert fakebz.getbugs(["123456", "CVE-1234-FAKE"]) == [] +def test_getbug_alias(): + """ + Test that `getbug()` includes the alias in `include_fields` + """ + fakebz = tests.mockbackend.make_bz( + bug_get_args=None, + bug_get_return="data/mockreturn/test_query_cve_getbug.txt") + bug = fakebz.getbug("CVE-1234-5678", include_fields=["id"]) + assert bug.alias == ["CVE-1234-5678"] + assert bug.id == 123456 + + def mock_bug_get(bug_ids, aliases, paramdict): + assert bug_ids == [] + assert aliases == ["CVE-1234-5678"] + assert "alias" in paramdict.get("include_fields", []) + return {"bugs": [bug.get_raw_data()]} + + backend = getattr(fakebz, "_backend") + setattr(backend, "bug_get", mock_bug_get) + + fakebz.getbug("CVE-1234-5678", include_fields=["id"]) + + def test_bug_getattr(): fakebz = tests.mockbackend.make_bz( bug_get_args=None, @@ -112,7 +129,7 @@ def test_bug_getattr(): bug.autorefresh = True summary = bug.summary - del(bug.__dict__["summary"]) + del bug.__dict__["summary"] # Trigger autorefresh assert bug.summary == summary @@ -167,6 +184,10 @@ def _get_fake_bug(apiname): assert bug.get_flags("NOPE") is None assert bug.get_flag_status("NOPE") is None + # bug.setsummary test + bug = _get_fake_bug("setsummary") + bug.setsummary("My new summary") + # Minor get_history_raw wrapper fakebz = tests.mockbackend.make_bz(rhbz=True, bug_history_args="data/mockargs/test_bug_api_history.txt", @@ -183,6 +204,7 @@ def _get_fake_bug(apiname): # Stub API testing bug = fakebz.getbug(1165434) bug.get_history_raw() + bug.get_comments() bug.getcomments() # Some hackery to hit a few attachment code paths @@ -190,3 +212,12 @@ def _get_fake_bug(apiname): attachments = bug.get_attachments() bug.attachments = attachments assert [469147, 470041, 502352] == bug.get_attachment_ids() + + +def test_bug_weburl(): + fakebz = tests.mockbackend.make_bz( + bug_get_args=None, + bug_get_return="data/mockreturn/test_getbug_rhel.txt") + bug_id = 1165434 + bug = fakebz.getbug(bug_id) + assert bug.weburl == f"https:///show_bug.cgi?id={bug_id}" diff --git a/tests/test_api_misc.py b/tests/test_api_misc.py index 75814e35..30889887 100644 --- a/tests/test_api_misc.py +++ b/tests/test_api_misc.py @@ -44,6 +44,7 @@ def test_fixurl(): "https://example.com/xmlrpc.cgi") assert (bugzilla.Bugzilla.fix_url("http://example.com/somepath.cgi") == "http://example.com/somepath.cgi") + assert bugzilla.Bugzilla.fix_url("http:///foo") == "http:///foo" def testPostTranslation(): @@ -164,6 +165,7 @@ def testStandardQuery(): } assert bz4.url_to_query(url) == query + # pylint: disable=use-implicit-booleaness-not-comparison # Test with unknown URL assert bz4.url_to_query("https://example.com") == {} @@ -304,3 +306,12 @@ def test_query_url_fail(): bz.query(query) except Exception as e: assert checkstr not in str(e) + + +def test_query_return_extra(): + bz = tests.mockbackend.make_bz(version="5.1.0", + bug_search_args=None, + bug_search_return="data/mockreturn/test_query1.txt") + dummy, extra = bz.query_return_extra({}) + assert extra['limit'] == 0 + assert extra['FOOFAKEVALUE'] == "hello" diff --git a/tests/test_backend_rest.py b/tests/test_backend_rest.py new file mode 100644 index 00000000..14836975 --- /dev/null +++ b/tests/test_backend_rest.py @@ -0,0 +1,67 @@ +from types import MethodType + +from bugzilla._backendrest import _BackendREST +from bugzilla._session import _BugzillaSession + + +class TestGetBug: + @property + def session(self): + return _BugzillaSession(url="http://example.com", + user_agent="py-bugzilla-test", + sslverify=False, + cert=None, + tokencache={}, + api_key="", + is_redhat_bugzilla=False) + + @property + def backend(self): + return _BackendREST(url="http://example.com", + bugzillasession=self.session) + + def test_getbug__not_permissive(self): + backend = self.backend + + def _assertion(self, *args): + self.assertion_called = True + assert args and args[0] == url + + setattr(backend, "_get", MethodType(_assertion, backend)) + + for _ids, aliases, url in ( + (1, None, "/bug/1"), + ([1], [], "/bug/1"), + (None, "CVE-1999-0001", "/bug/CVE-1999-0001"), + ([], ["CVE-1999-0001"], "/bug/CVE-1999-0001"), + (1, "CVE-1999-0001", "/bug"), + ([1, 2], None, "/bug") + ): + backend.assertion_called = False + + backend.bug_get(_ids, aliases, {}) + + assert backend.assertion_called is True + + def test_getbug__permissive(self): + backend = self.backend + + def _assertion(self, *args): + self.assertion_called = True + assert args and args[0] == url and args[1] == params + + setattr(backend, "_get", MethodType(_assertion, backend)) + + for _ids, aliases, url, params in ( + (1, None, "/bug", {"id": [1], "alias": None}), + ([1], [], "/bug", {"id": [1], "alias": []}), + (None, "CVE-1999-0001", "/bug", {"alias": ["CVE-1999-0001"], "id": None}), + ([], ["CVE-1999-0001"], "/bug", {"alias": ["CVE-1999-0001"], "id": []}), + (1, "CVE-1999-0001", "/bug", {"id": [1], "alias": ["CVE-1999-0001"]}), + ([1, 2], None, "/bug", {"id": [1, 2], "alias": None}) + ): + backend.assertion_called = False + + backend.bug_get(_ids, aliases, {"permissive": True}) + + assert backend.assertion_called is True diff --git a/tests/test_base.py b/tests/test_base.py new file mode 100644 index 00000000..d62428e5 --- /dev/null +++ b/tests/test_base.py @@ -0,0 +1,20 @@ +from bugzilla.base import Bugzilla + + +def test_build_createbug(): + bz = Bugzilla(url=None) + + args = {"product": "Ubuntu 33⅓", "summary": "Hello World", "alias": "CVE-2024-0000"} + result = bz.build_createbug(**args) + assert result == args + + result = bz.build_createbug(groups=None, **args) + assert result == args + + args["groups"] = [] + result = bz.build_createbug(**args) + assert result == args + + args["groups"] += ["the-group"] + result = bz.build_createbug(**args) + assert result == args diff --git a/tests/test_cli_attach.py b/tests/test_cli_attach.py index e0427668..584858f1 100644 --- a/tests/test_cli_attach.py +++ b/tests/test_cli_attach.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - # This work is licensed under the GNU GPLv2 or later. # See the COPYING file in the top-level directory. @@ -51,6 +49,18 @@ def test_attach(run_cli): out = run_cli(cmd, fakebz, stdin=attachcontent) assert "Created attachment 1557949 on bug 123456" in out + # Test --field passthrough + cmd = "bugzilla attach 123456 --file=%s " % attachfile + cmd += "--field=is_obsolete=1 " + cmd += "--field-json " + cmd += ('\'{"flags": [{"name": "review"' + ', "requestee": "crobinso@redhat.com", "status": "-"}]}\'') + fakebz = tests.mockbackend.make_bz( + bug_attachment_create_args="data/mockargs/test_attach3.txt", + bug_attachment_create_return={'ids': [1557949]}) + out = run_cli(cmd, fakebz) + assert "Created attachment 1557949 on bug 123456" in out + def _test_attach_get(run_cli): # Hit error when using ids with --get* @@ -60,7 +70,7 @@ def _test_attach_get(run_cli): assert "not used for" in out # Basic --get ATTID usage - filename = u"Klíč memorial test file.txt" + filename = "Klíč memorial test file.txt" cmd = "bugzilla attach --get 112233" fakebz = tests.mockbackend.make_bz( bug_attachment_get_args="data/mockargs/test_attach_get1.txt", diff --git a/tests/test_cli_login.py b/tests/test_cli_login.py index 967a92c2..fad2b7dd 100644 --- a/tests/test_cli_login.py +++ b/tests/test_cli_login.py @@ -61,16 +61,32 @@ def test_login(run_cli): # Returns success for logged_in check and hits a tokenfile line cmd = "bugzilla --ensure-logged-in " cmd += "login FOO BAR" + tmp = tempfile.NamedTemporaryFile() fakebz = tests.mockbackend.make_bz( - bz_kwargs={"use_creds": True}, + bz_kwargs={"use_creds": True, "tokenfile": tmp.name}, user_login_args="data/mockargs/test_login.txt", user_login_return={'id': 1234, 'token': 'my-fake-token'}, user_get_args=None, user_get_return={}) + fakebz.connect("https://example.com") out = run_cli(cmd, fakebz) assert "Token cache saved" in out assert fakebz.tokenfile in out assert "Consider using bugzilla API" in out + tests.utils.diff_compare(open(tmp.name).read(), + "data/clioutput/tokenfile.txt") + + # Returns success for logged_in check and hits another tokenfile line + cmd = "bugzilla --ensure-logged-in " + cmd += "login FOO BAR" + fakebz = tests.mockbackend.make_bz( + bz_kwargs={"use_creds": True, "tokenfile": None}, + user_login_args="data/mockargs/test_login.txt", + user_login_return={'id': 1234, 'token': 'my-fake-token'}, + user_get_args=None, + user_get_return={}) + out = run_cli(cmd, fakebz) + assert "Token not saved" in out def test_interactive_login(monkeypatch, run_cli): diff --git a/tests/test_cli_misc.py b/tests/test_cli_misc.py index fd042cfe..5a898bef 100644 --- a/tests/test_cli_misc.py +++ b/tests/test_cli_misc.py @@ -9,17 +9,15 @@ Test miscellaneous CLI bits to get build out our code coverage """ -from __future__ import print_function - import base64 import datetime import json +import xmlrpc.client import pytest import requests import bugzilla -from bugzilla._compatimports import Binary, DateTime import tests import tests.mockbackend @@ -40,6 +38,13 @@ def testVersion(run_cli): assert out.strip() == bugzilla.__version__ +def testCookiefileDeprecated(run_cli): + with pytest.raises(TypeError) as e: + run_cli("bugzilla --cookiefile foobar login", + None, expectfail=True) + assert "cookiefile is deprecated" in str(e) + + def testPositionalArgs(run_cli): # Make sure cli correctly rejects ambiguous positional args out = run_cli("bugzilla login --xbadarg foo", @@ -113,8 +118,8 @@ def test_json_xmlrpc(run_cli): bugid = 1165434 data = {"bugs": [{ 'id': bugid, - 'timetest': DateTime(dateobj), - 'binarytest': Binary(attachdata), + 'timetest': xmlrpc.client.DateTime(dateobj), + 'binarytest': xmlrpc.client.Binary(attachdata), }]} fakebz = tests.mockbackend.make_bz( diff --git a/tests/test_cli_modify.py b/tests/test_cli_modify.py index 6f4e88f2..e3731451 100644 --- a/tests/test_cli_modify.py +++ b/tests/test_cli_modify.py @@ -65,3 +65,30 @@ def test_modify(run_cli): bug_update_return={}) out = run_cli(cmd, fakebz) assert not out + + # Modify with a slew of misc opt coverage + cmd = "bugzilla modify 1165434 " + cmd += "--assigned_to foo@example.com --qa_contact qa@example.com " + cmd += "--product newproduct " + cmd += "--blocked +1234 --blocked -1235 --blocked = " + cmd += "--url https://example.com " + cmd += "--cc=+bar@example.com --cc=-steve@example.com " + cmd += "--dependson=+2234 --dependson=-2235 --dependson = " + cmd += "--groups +foogroup " + cmd += "--keywords +newkeyword --keywords=-byekeyword --keywords = " + cmd += "--os windows --arch mips " + cmd += "--priority high --severity low " + cmd += "--summary newsummary --version 1.2.3 " + cmd += "--reset-assignee --reset-qa-contact " + cmd += "--alias fooalias " + cmd += "--target_release 1.2.4 --target_milestone beta " + cmd += "--devel_whiteboard =DEVBOARD --internal_whiteboard =INTBOARD " + cmd += "--qa_whiteboard =QABOARD " + cmd += "--comment-tag FOOTAG --field bar=foo " + cmd += '--field-json \'{"cf_verified": ["Tested"], "cf_blah": {"1": 2}}\' ' + cmd += "--minor-update " + fakebz = tests.mockbackend.make_bz(rhbz=True, + bug_update_args="data/mockargs/test_modify5.txt", + bug_update_return={}) + out = run_cli(cmd, fakebz) + assert not out diff --git a/tests/test_cli_new.py b/tests/test_cli_new.py index 6ab25884..86e153ab 100644 --- a/tests/test_cli_new.py +++ b/tests/test_cli_new.py @@ -11,7 +11,7 @@ ############################### def test_new(run_cli): - # Bunch of options + # Test a simpler creation cmd = "bugzilla new --product FOOPROD --component FOOCOMP " cmd += "--summary 'Hey this is the title!' " cmd += "--comment 'This is the first comment!\nWith newline & stuff.' " @@ -26,3 +26,28 @@ def test_new(run_cli): bug_get_return="data/mockreturn/test_getbug.txt") out = run_cli(cmd, fakebz) tests.utils.diff_compare(out, "data/clioutput/test_new1.txt") + + # Test every option + cmd = "bugzilla new --product FOOPROD --component FOOCOMP " + cmd += "--summary 'Hey this is the title!' " + cmd += "--comment 'This is the first comment!\nWith newline & stuff.' " + cmd += "--keywords ADDKEY --groups FOOGROUP,BARGROUP " + cmd += "--blocked 12345,6789 --cc foo@example.com --cc bar@example.com " + cmd += "--dependson dependme --private " + cmd += "--os linux --arch mips --severity high --priority low " + cmd += "--url https://some.example.com " + cmd += "--version 5.6.7 --alias somealias " + cmd += "--sub-component FOOCOMP " + cmd += "--assignee foo@example.com --qa_contact qa@example.com " + cmd += "--comment-tag FOO " + cmd += "--field foo=bar " + cmd += '--field-json \'{"cf_verified": ["Tested"], "cf_blah": {"1": 2}}\' ' + + fakebz = tests.mockbackend.make_bz( + bug_create_args="data/mockargs/test_new2.txt", + bug_create_return={"id": 1694158}, + bug_get_args=None, + bug_get_return="data/mockreturn/test_getbug.txt", + rhbz=True) + out = run_cli(cmd, fakebz) + tests.utils.diff_compare(out, "data/clioutput/test_new2.txt") diff --git a/tests/test_cli_query.py b/tests/test_cli_query.py index 3fa4cc85..5a751a53 100644 --- a/tests/test_cli_query.py +++ b/tests/test_cli_query.py @@ -15,12 +15,18 @@ ################################# def test_query(run_cli): - # bad field option + # bad --field option fakebz = tests.mockbackend.make_bz() cmd = "bugzilla query --field FOO" out = run_cli(cmd, fakebz, expectfail=True) assert "Invalid field argument" in out + # bad --field-json option + fakebz = tests.mockbackend.make_bz() + cmd = "bugzilla query --field-json='{1: 2}'" + out = run_cli(cmd, fakebz, expectfail=True) + assert "Invalid field-json" in out + # Simple query with some comma opts cmd = "bugzilla query " cmd += "--product foo --component foo,bar --bug_id 1234,2480" @@ -104,6 +110,7 @@ def test_query(run_cli): # Test --status EOL and --oneline, and some --field usage cmd = "bugzilla query --status EOL --oneline " cmd += "--field FOO=1 --field=BAR=WIBBLE " + cmd += '--field-json \'{"cf_verified": ["Tested"], "cf_blah": {"1": 2}}\' ' fakebz = tests.mockbackend.make_bz( bug_search_args="data/mockargs/test_query6.txt", bug_search_return="data/mockreturn/test_getbug_rhel.txt", @@ -147,3 +154,27 @@ def test_query(run_cli): tests.utils.diff_compare(tests.utils.sanitize_json(out), "data/clioutput/test_query9.txt") assert json.loads(out) + + + # Test every remaining option + cmd = "bugzilla query " + cmd += "--sub-component FOOCOMP " + cmd += "--version 5.6.7 --reporter me@example.com " + cmd += "--summary 'search summary' " + cmd += "--assignee bar@example.com " + cmd += "--blocked 12345 --dependson 23456 " + cmd += "--keywords FOO --keywords_type substring " + cmd += "--url https://example.com --url_type sometype " + cmd += "--target_release foo --target_milestone bar " + cmd += "--quicksearch 1 --savedsearch 2 --savedsearch-sharer-id 3 " + cmd += "--tags +foo --flag needinfo --alias somealias " + cmd += "--devel_whiteboard DEVBOARD " + cmd += "--priority wibble " + cmd += "--fixed_in 5.5.5 --fixed_in_type substring " + cmd += "--whiteboard FOO --status_whiteboard_type substring " + fakebz = tests.mockbackend.make_bz( + bug_search_args="data/mockargs/test_query10.txt", + bug_search_return="data/mockreturn/test_getbug_rhel.txt", + rhbz=True) + out = run_cli(cmd, fakebz) + tests.utils.diff_compare(out, "data/clioutput/test_query10.txt") diff --git a/tests/test_ro_functional.py b/tests/test_ro_functional.py index 03761bdc..6bb770da 100644 --- a/tests/test_ro_functional.py +++ b/tests/test_ro_functional.py @@ -1,5 +1,3 @@ -# -*- encoding: utf-8 -*- - # # Copyright Red Hat, Inc. 2012 # @@ -10,8 +8,12 @@ """ Unit tests that do readonly functional tests against real bugzilla instances. """ +from xmlrpc.client import Fault + +import pytest import bugzilla +from bugzilla.exceptions import BugzillaError import tests @@ -19,10 +21,11 @@ "https://bugzilla.redhat.com") -def _open_bz(url, **kwargs): +def _open_bz(url, bzclass=None, **kwargs): if "use_creds" not in kwargs: kwargs["use_creds"] = False - return tests.utils.open_functional_bz(bugzilla.Bugzilla, url, kwargs) + return tests.utils.open_functional_bz(bzclass or bugzilla.Bugzilla, + url, kwargs) def _check(out, mincount, expectstr): @@ -39,19 +42,69 @@ def _test_version(bz, bzversion): assert bz.bz_ver_minor == bzversion[1] +def test_bugzilla_override(): + class MyBugzilla(bugzilla.Bugzilla): + pass + + bz = _open_bz("bugzilla.redhat.com", bzclass=MyBugzilla) + assert bz.__class__ is MyBugzilla + assert bz._is_redhat_bugzilla is True # pylint: disable=protected-access + + +# See also: tests/integration/ro_api_test.py::test_rest_xmlrpc_detection def test_rest_xmlrpc_detection(): # The default: use XMLRPC bz = _open_bz("bugzilla.redhat.com") assert bz.is_xmlrpc() assert "/xmlrpc.cgi" in bz.url + assert bz.__class__ is bugzilla.RHBugzilla # See /rest in the URL, so use REST bz = _open_bz("bugzilla.redhat.com/rest") assert bz.is_rest() + with pytest.raises(bugzilla.BugzillaError) as e: + dummy = bz._proxy # pylint: disable=protected-access + assert "raw XMLRPC access is not provided" in str(e) # See /xmlrpc.cgi in the URL, so use XMLRPC bz = _open_bz("bugzilla.redhat.com/xmlrpc.cgi") assert bz.is_xmlrpc() + assert bz._proxy # pylint: disable=protected-access + + +# See also: tests/integration/ro_api_test.py::test_apikey_error_scraping +def test_apikey_error_scraping(): + # Ensure the API key does not leak into any requests exceptions + fakekey = "FOOBARMYKEY" + + with pytest.raises(Exception) as e: + _open_bz("https://bugzilla.redhat.nopedontexist", + force_rest=True, api_key=fakekey) + assert fakekey not in str(e.value) + + with pytest.raises(Exception) as e: + _open_bz("https://bugzilla.redhat.nopedontexist", + force_xmlrpc=True, api_key=fakekey) + assert fakekey not in str(e.value) + + with pytest.raises(Exception) as e: + _open_bz("https://httpstat.us/502&foo", + force_xmlrpc=True, api_key=fakekey) + assert "Client Error" in str(e.value) + assert fakekey not in str(e.value) + + with pytest.raises(Exception) as e: + _open_bz("https://httpstat.us/502&foo", + force_rest=True, api_key=fakekey) + assert "Client Error" in str(e.value) + assert fakekey not in str(e.value) + + +# See also: tests/integration/ro_api_test.py::test_xmlrpc_bad_url +def test_xmlrpc_bad_url(): + with pytest.raises(bugzilla.BugzillaError) as e: + _open_bz("https://example.com/#xmlrpc") + assert "URL may not be an XMLRPC URL" in str(e) ################### @@ -92,6 +145,7 @@ def test_gentoo(backends): ################## +# See also: tests/integration/ro_cli_test.py::test_get_products def testInfoProducts(run_cli, backends): bz = _open_bz(REDHAT_URL, **backends) @@ -99,6 +153,7 @@ def testInfoProducts(run_cli, backends): _check(out, 123, "Virtualization Tools") +# See also: tests/integration/ro_cli_test.py::test_get_components def testInfoComps(run_cli, backends): bz = _open_bz(REDHAT_URL, **backends) @@ -106,6 +161,7 @@ def testInfoComps(run_cli, backends): _check(out, 8, "virtinst") +# See also: tests/integration/ro_cli_test.py::test_get_versions def testInfoVers(run_cli, backends): bz = _open_bz(REDHAT_URL, **backends) @@ -113,6 +169,7 @@ def testInfoVers(run_cli, backends): _check(out, 17, "rawhide") +# See also: tests/integration/ro_cli_test.py::test_get_component_owners def testInfoCompOwners(run_cli, backends): bz = _open_bz(REDHAT_URL, **backends) @@ -121,6 +178,7 @@ def testInfoCompOwners(run_cli, backends): _check(out, None, "libvirt: Libvirt Maintainers") +# See also: tests/integration/ro_cli_test.py::test_query def testQuery(run_cli, backends): bz = _open_bz(REDHAT_URL, **backends) @@ -141,6 +199,7 @@ def testQuery(run_cli, backends): l2 == expectbug]) +# See also: tests/integration/ro_cli_test.py::test_query_full def testQueryFull(run_cli, backends): bz = _open_bz(REDHAT_URL, **backends) @@ -149,6 +208,7 @@ def testQueryFull(run_cli, backends): _check(out, 60, "end-of-life (EOL)") +# See also: tests/integration/ro_cli_test.py::test_query_raw def testQueryRaw(run_cli, backends): bz = _open_bz(REDHAT_URL, **backends) @@ -157,6 +217,7 @@ def testQueryRaw(run_cli, backends): _check(out, 70, "ATTRIBUTE[whiteboard]: bzcl34nup") +# See also: tests/integration/ro_cli_test.py::test_query_oneline def testQueryOneline(run_cli, backends): bz = _open_bz(REDHAT_URL, **backends) @@ -173,6 +234,7 @@ def testQueryOneline(run_cli, backends): assert " CVE-2011-2527" in out +# See also: tests/integration/ro_cli_test.py::test_query_extra def testQueryExtra(run_cli, backends): bz = _open_bz(REDHAT_URL, **backends) @@ -182,6 +244,7 @@ def testQueryExtra(run_cli, backends): assert " +Status Whiteboard: bzcl34nup" in out +# See also: tests/integration/ro_cli_test.py::test_query_format def testQueryFormat(run_cli, backends): bz = _open_bz(REDHAT_URL, **backends) @@ -200,16 +263,17 @@ def testQueryFormat(run_cli, backends): # Unicode in this bug's summary args = "--bug_id 522796 --outputformat \"%{summary}\"" out = run_cli("bugzilla query %s" % args, bz) - assert u"V34 — system" in out + assert "V34 — system" in out +# See also: tests/integration/ro_cli_test.py::test_query_url def testQueryURL(run_cli, backends): bz = _open_bz(REDHAT_URL, **backends) qurl = ("/buglist.cgi?f1=creation_ts" "&list_id=973582&o1=greaterthaneq&classification=Fedora&" "o2=lessthaneq&query_format=advanced&f2=creation_ts" - "&v1=2010-01-01&component=python-bugzilla&v2=2011-01-01" + "&v1=2010-01-01&component=python-bugzilla&v2=2010-06-01" "&product=Fedora") url = REDHAT_URL @@ -218,7 +282,7 @@ def testQueryURL(run_cli, backends): else: url += qurl out = run_cli("bugzilla query --from-url \"%s\"" % url, bz) - _check(out, 22, "#553878 CLOSED") + _check(out, 10, "#553878 CLOSED") def testQueryFixedIn(run_cli, backends): @@ -240,6 +304,7 @@ def testQueryExtrafieldPool(run_cli, backends): assert "current_sprint_id" in out2 +# See also: tests/integration/ro_api_test.py::test_get_component_detail def testComponentsDetails(backends): """ Fresh call to getcomponentsdetails should properly refresh @@ -249,6 +314,7 @@ def testComponentsDetails(backends): assert bool(bz.getcomponentsdetails("Red Hat Developer Toolset")) +# See also: tests/integration/ro_api_test.py::test_get_bug_alias def testGetBugAlias(backends): """ getbug() works if passed an alias @@ -259,10 +325,50 @@ def testGetBugAlias(backends): assert bug.bug_id == 720773 -def testQuerySubComponent(run_cli, backends): +# See also: tests/integration/ro_api_test.py::test_get_bug_404 +def testGetBug404(backends): + """ + getbug() is expected to raise an error, if a bug ID or alias does not exist + """ + bz = _open_bz(REDHAT_URL, **backends) + + try: + bz.getbug(100000000) + except Fault as error: # XMLRPC API + assert error.faultCode == 101 + except BugzillaError as error: # REST API + assert error.code == 101 + else: + raise AssertionError("No exception raised") + + +# See also: tests/integration/ro_api_test.py::test_get_bug_alias_404 +def testGetBugAlias404(backends): + """ + getbug() is expected to raise an error, if a bug ID or alias does not exist + """ + bz = _open_bz(REDHAT_URL, **backends) + + try: + bz.getbug("CVE-1234-4321") + except Fault as error: # XMLRPC API + assert error.faultCode == 100 + except BugzillaError as error: # REST API + assert error.code == 100 + else: + raise AssertionError("No exception raised") + + +# See also: tests/integration/ro_api_test.py::test_get_bug_alias_included_field +def testGetBugAliasIncludedField(backends): bz = _open_bz(REDHAT_URL, **backends) - tests.utils.skip_if_rest(bz, "Not working on REST, not sure why yet") + bug = bz.getbug("CVE-2011-2527", include_fields=["id"]) + assert bug.bug_id == 720773 + + +def testQuerySubComponent(run_cli, backends): + bz = _open_bz(REDHAT_URL, **backends) # Test special error wrappers in bugzilla/_cli.py out = run_cli("bugzilla query --product 'Red Hat Enterprise Linux 7' " @@ -271,6 +377,7 @@ def testQuerySubComponent(run_cli, backends): assert "#1060931 " in out +# See also: tests/integration/ro_api_test.py::test_get_bug_fields def testBugFields(backends): bz = _open_bz(REDHAT_URL, **backends) @@ -280,6 +387,15 @@ def testBugFields(backends): assert set(bz.bugfields) == set(["product", "bug_status"]) +# See also: tests/integration/ro_api_test.py::test_get_product +def testProductGetMisc(backends): + bz = _open_bz(REDHAT_URL, **backends) + + assert bz.product_get(ptype="enterable", include_fields=["id"]) + assert bz.product_get(ptype="selectable", include_fields=["name"]) + + +# See also: tests/integration/ro_api_test.py::test_query_autorefresh def testBugAutoRefresh(backends): bz = _open_bz(REDHAT_URL, **backends) @@ -301,6 +417,7 @@ def testBugAutoRefresh(backends): assert "adjust your include_fields" in str(e) +# See also (in part): tests/integration/ro_api_test.py::test_get_bug_exclude_fields def testExtraFields(backends): bz = _open_bz(REDHAT_URL, **backends) @@ -324,6 +441,7 @@ def testExternalBugsOutput(run_cli, backends): assert "External bug: https://bugs.launchpad.net/bugs/1203576" in out +# See also: tests/integration/ro_cli_test.py::test_get_active_components def testActiveComps(run_cli, backends): bz = _open_bz(REDHAT_URL, **backends) @@ -335,6 +453,7 @@ def testActiveComps(run_cli, backends): assert "virtinst" not in out +# See also: tests/integration/ro_cli_test.py::test_fails def testFaults(run_cli, backends): bz = _open_bz(REDHAT_URL, **backends) @@ -355,9 +474,45 @@ def testFaults(run_cli, backends): assert "--nosslverify" in out +# See also: tests/integration/ro_api_test.py::test_login_stubs +def test_login_stubs(backends): + bz = _open_bz(REDHAT_URL, **backends) + + # In 2024 bugzilla.redhat.com disabled User.login and User.logout APIs + # for xmlrpc API + + with pytest.raises(bugzilla.BugzillaError) as e: + bz.login("foo", "bar") + assert "Login failed" in str(e) + + is_rest = bz.is_rest() + is_xmlrpc = bz.is_xmlrpc() + + msg = None + try: + bz.logout() + except Exception as error: + msg = str(error) + + if is_rest and msg: + raise AssertionError("didn't expect exception: %s" % msg) + if is_xmlrpc: + assert "'User.logout' was not found" in str(msg) + + def test_redhat_version(backends): bzversion = (5, 0) bz = _open_bz(REDHAT_URL, **backends) if not tests.CLICONFIG.REDHAT_URL: _test_version(bz, bzversion) + + +# See also: tests/integration/ro_api_test.py::test_bug_url +def test_bug_misc(backends): + bz = _open_bz(REDHAT_URL, **backends) + + # Ensure weburl is generated consistently whether + # we are using XMLRPC or REST + bug = bz.getbug(720773) + assert bug.weburl == "https://bugzilla.redhat.com/show_bug.cgi?id=720773" diff --git a/tests/test_rw_functional.py b/tests/test_rw_functional.py index f00c76fc..600fa7ed 100644 --- a/tests/test_rw_functional.py +++ b/tests/test_rw_functional.py @@ -9,8 +9,6 @@ Unit tests that do permanent functional against a real bugzilla instances. """ -from __future__ import print_function - import datetime import inspect import os @@ -24,8 +22,12 @@ import tests.utils -RHURL = tests.CLICONFIG.REDHAT_URL or "partner-bugzilla.redhat.com" +RHURL = tests.CLICONFIG.REDHAT_URL or "bugzilla.stage.redhat.com" + +################## +# helper methods # +################## def _split_int(s): return [int(i) for i in s.split(",")] @@ -52,30 +54,41 @@ def _check_have_admin(bz): return ret -def test0LoggedInNoCreds(): - bz = _open_bz(use_creds=False) - assert not bz.logged_in +def _set_have_dev(bug, assigned_to): + # This will only take effect if the logged in user has fedora dev perms + have_dev = bug.assigned_to == assigned_to + bug._testsuite_have_dev = have_dev # pylint: disable=protected-access -def test0ClassDetection(): - bz = bugzilla.Bugzilla(RHURL, use_creds=False) - assert bz.__class__ is bugzilla.RHBugzilla +def _bug_close(run_cli, bug): + # Pre-close it + bz = bug.bugzilla + run_cli("bugzilla modify --close NOTABUG %s --minor-update" % bug.id, bz) + bug.refresh() + assert bug.status == "CLOSED" + assert bug.resolution == "NOTABUG" def _makebug(run_cli, bz): + """ + Make a basic bug that the logged in user can maximally manipulate + """ + product = "Fedora" component = "python-bugzilla" version = "rawhide" + assigned_to = "triage@lists.fedoraproject.org" summary = ("python-bugzilla test basic bug %s" % datetime.datetime.today()) newout = run_cli("bugzilla new " - "--product Fedora --component %s --version %s " - "--summary \"%s\" " + f"--product '{product}' " + f"--component '{component}' " + f"--version '{version}' " + f"--assigned_to '{assigned_to}' " + f"--summary \"{summary}\" " "--comment \"Test bug from the python-bugzilla test suite\" " - "--outputformat \"%%{bug_id}\"" % - (component, version, summary), bz) + "--outputformat \"%{bug_id}\"", bz) - assert len(newout.splitlines()) == 1 - bugid = int(newout.splitlines()[0]) + bugid = int(newout.splitlines()[-1]) bug = bz.getbug(bugid) print("\nCreated bugid: %s" % bug.id) @@ -83,84 +96,133 @@ def _makebug(run_cli, bz): assert bug.version == version assert bug.summary == summary + _set_have_dev(bug, assigned_to) + _bug_close(run_cli, bug) + return bug -def test03NewBugBasic(run_cli, backends): - """ - Create a bug with minimal amount of fields, then close it - """ - bz = _open_bz(**backends) - bug = _makebug(run_cli, bz) +def _check_have_dev(bug): + funcname = inspect.stack()[1][3] + have_dev = bug._testsuite_have_dev # pylint: disable=protected-access - # Verify hasattr works - assert hasattr(bug, "id") - assert hasattr(bug, "bug_id") + if not have_dev: + print("\nNo dev privs, reduced testing of %s" % funcname) + return have_dev - # Close the bug - run_cli("bugzilla modify --close NOTABUG %s" % bug.id, bz) - bug.refresh() - assert bug.status == "CLOSED" - assert bug.resolution == "NOTABUG" +class _BugCache: + cache = {} -def test04NewBugAllFields(run_cli, backends): + @classmethod + def get_bug(cls, run_cli, bz): + key = bz.is_xmlrpc() and "xmlrpc" or "rest" + if key not in cls.cache: + cls.cache[key] = _makebug(run_cli, bz) + return cls.cache[key] + + +def _make_subcomponent_bug(run_cli, bz): """ - Create a bug using all 'new' fields, check some values, close it + Helper for creating a bug that can handle rhbz sub components """ - bz = _open_bz(**backends) - summary = ("python-bugzilla test manyfields bug %s" % datetime.datetime.today()) + assigned_to = "triage@lists.fedoraproject.org" url = "http://example.com" osval = "Windows" cc = "triage@lists.fedoraproject.org" + assigned_to = "triage@lists.fedoraproject.org" blocked = "461686,461687" dependson = "427301" comment = "Test bug from python-bugzilla test suite" - sub_component = "Command-line tools (RHEL6)" + # We use this product+component to test sub_component + product = "Bugzilla" + component = "Extensions" + version = "5.0" + sub_component = "AgileTools" alias = "pybz-%s" % datetime.datetime.today().strftime("%s") newout = run_cli("bugzilla new " - "--product 'Red Hat Enterprise Linux 6' --version 6.0 " - "--component lvm2 --sub-component '%s' " - "--summary \"%s\" " - "--comment \"%s\" " - "--url %s --severity Urgent --priority Low --os %s " - "--arch ppc --cc %s --blocked %s --dependson %s " - "--alias %s " - "--outputformat \"%%{bug_id}\"" % - (sub_component, summary, comment, url, - osval, cc, blocked, dependson, alias), bz) - - assert len(newout.splitlines()) == 1 - - bugid = int(newout.splitlines()[0]) + f"--product '{product}' " + f"--version '{version}' " + f"--component '{component}' " + f"--sub-component '{sub_component}' " + f"--summary \"{summary}\" " + f"--comment \"{comment}\" " + f"--url {url} " + f"--os {osval} " + f"--cc {cc} " + f"--assigned_to {assigned_to} " + f"--blocked {blocked} " + f"--dependson {dependson} " + f"--alias {alias} " + "--arch ppc --severity Urgent --priority Low " + "--outputformat \"%{bug_id}\"", bz) + + bugid = int(newout.splitlines()[-1]) bug = bz.getbug(bugid, extra_fields=["sub_components"]) print("\nCreated bugid: %s" % bugid) + _set_have_dev(bug, assigned_to) + have_dev = _check_have_dev(bug) + assert bug.summary == summary assert bug.bug_file_loc == url assert bug.op_sys == osval - assert bug.blocks == _split_int(blocked) - assert bug.depends_on == _split_int(dependson) assert all([e in bug.cc for e in cc.split(",")]) assert bug.longdescs[0]["text"] == comment - assert bug.sub_components == {"lvm2": [sub_component]} + assert bug.sub_components == {component: [sub_component]} assert bug.alias == [alias] - # Close the bug + if have_dev: + assert bug.blocks == _split_int(blocked) + assert bug.depends_on == _split_int(dependson) + else: + # Using a non-dev account seems to fail to set these at bug create time + assert bug.blocks == [] + assert bug.depends_on == [] + + _bug_close(run_cli, bug) + + return bug + - # RHBZ makes it difficult to provide consistent semantics for - # 'alias' update: - # https://bugzilla.redhat.com/show_bug.cgi?id=1173114 - # alias += "-closed" +############## +# test cases # +############## + +# See also: tests/integration/rw_api_test.py::test_logged_in_no_creds +def test0LoggedInNoCreds(backends): + bz = _open_bz(**backends, use_creds=False) + assert not bz.logged_in + + +def test0ClassDetection(): + bz = bugzilla.Bugzilla(RHURL, use_creds=False) + assert bz.__class__ is bugzilla.RHBugzilla + + +# See also: tests/integration/rw_api_test.py::test_create_bug +# tests/integration/rw_api_test.py::test_create_bug_alias +# tests/integration/rw_api_test.py::test_update_bug +def test04NewBugAllFields(run_cli, backends): + """ + Create a bug using all 'new' fields, check some values, close it + """ + bz = _open_bz(**backends) + bug = _make_subcomponent_bug(run_cli, bz) + + # Verify hasattr works + assert hasattr(bug, "id") + assert hasattr(bug, "bug_id") + + # Close the bug run_cli("bugzilla modify " "--close WONTFIX %s " % - bugid, bz) + bug.id, bz) bug.refresh() assert bug.status == "CLOSED" assert bug.resolution == "WONTFIX" - assert bug.alias == [alias] # Check bug's minimal history ret = bug.get_history_raw() @@ -173,44 +235,53 @@ def test05ModifyStatus(run_cli, backends): Modify status and comment fields for an existing bug """ bz = _open_bz(**backends) - bugid = "663674" - cmd = "bugzilla modify %s " % bugid - - bug = bz.getbug(bugid) - - # We want to start with an open bug, so fix things - if bug.status == "CLOSED": - run_cli(cmd + "--status ASSIGNED", bz) - bug.refresh() - assert bug.status == "ASSIGNED" + bug = _BugCache.get_bug(run_cli, bz) + have_dev = _check_have_dev(bug) + cmd = "bugzilla modify %s " % bug.id origstatus = bug.status + perm_error = "not allowed to (un)mark comments" # Set to ON_QA with a private comment - status = "ON_QA" - comment = ("changing status to %s at %s" % - (status, datetime.datetime.today())) - run_cli(cmd + - "--status %s --comment \"%s\" --private" % (status, comment), bz) + try: + status = "ON_QA" + comment = ("changing status to %s at %s" % + (status, datetime.datetime.today())) + run_cli(cmd + + "--status %s --comment \"%s\" --private" % (status, comment), bz) - bug.refresh() - assert bug.status == status - assert bug.longdescs[-1]["is_private"] == 1 - assert bug.longdescs[-1]["text"] == comment + bug.refresh() + assert bug.status == status + assert bug.longdescs[-1]["is_private"] == 1 + assert bug.longdescs[-1]["text"] == comment + except RuntimeError as e: + if have_dev: + raise + assert perm_error in str(e) # Close bug as DEFERRED with a private comment - resolution = "DEFERRED" - comment = ("changing status to CLOSED=%s at %s" % - (resolution, datetime.datetime.today())) - run_cli(cmd + - "--close %s --comment \"%s\" --private" % - (resolution, comment), bz) + try: + resolution = "DEFERRED" + comment = ("changing status to CLOSED=%s at %s" % + (resolution, datetime.datetime.today())) + run_cli(cmd + + "--close %s --comment \"%s\" --private" % + (resolution, comment), bz) + + bug.refresh() + assert bug.status == "CLOSED" + assert bug.resolution == resolution + assert bug.comments[-1]["is_private"] == 1 + assert bug.comments[-1]["text"] == comment + except RuntimeError as e: + if have_dev: + raise + assert perm_error in str(e) + # Set to assigned + run_cli(cmd + "--status ASSIGNED", bz) bug.refresh() - assert bug.status == "CLOSED" - assert bug.resolution == resolution - assert bug.comments[-1]["is_private"] == 1 - assert bug.comments[-1]["text"] == comment + assert bug.status == "ASSIGNED" # Close bug as dup with no comment dupeid = "461686" @@ -224,13 +295,19 @@ def test05ModifyStatus(run_cli, backends): assert "marked as a duplicate" in bug.longdescs[-1]["text"] # bz.setstatus test - comment = ("adding lone comment at %s" % datetime.datetime.today()) - bug.setstatus("POST", comment=comment, private=True) - bug.refresh() - assert bug.longdescs[-1]["is_private"] == 1 - assert bug.longdescs[-1]["text"] == comment - assert bug.status == "POST" + try: + comment = ("adding lone comment at %s" % datetime.datetime.today()) + bug.setstatus("POST", comment=comment, private=True) + bug.refresh() + assert bug.longdescs[-1]["is_private"] == 1 + assert bug.longdescs[-1]["text"] == comment + assert bug.status == "POST" + except Exception as e: + if have_dev: + raise + assert perm_error in str(e) + # See also: tests/integration/rw_api_test.py::test_close_bug # bz.close test fixed_in = str(datetime.datetime.today()) bug.close("ERRATA", fixedin=fixed_in) @@ -239,6 +316,7 @@ def test05ModifyStatus(run_cli, backends): assert bug.resolution == "ERRATA" assert bug.fixed_in == fixed_in + # See also: tests/integration/rw_api_test.py::test_add_comment # bz.addcomment test comment = ("yet another test comment %s" % datetime.datetime.today()) bug.addcomment(comment, private=False) @@ -246,8 +324,11 @@ def test05ModifyStatus(run_cli, backends): assert bug.longdescs[-1]["text"] == comment assert bug.longdescs[-1]["is_private"] == 0 - # Confirm comments is same as getcomments + # Confirm comments is same as get_comments + assert bug.comments == bug.get_comments() + # This method will be removed in a future version assert bug.comments == bug.getcomments() + assert bug.get_comments() == bug.getcomments() # Reset state run_cli(cmd + "--status %s" % origstatus, bz) @@ -255,61 +336,76 @@ def test05ModifyStatus(run_cli, backends): assert bug.status == origstatus +# See also: tests/integration/rw_api_test.py::test_update_bug def test06ModifyEmails(run_cli, backends): """ Modify cc, assignee, qa_contact for existing bug """ bz = _open_bz(**backends) - bugid = "663674" - cmd = "bugzilla modify %s " % bugid - - bug = bz.getbug(bugid) + bug = _BugCache.get_bug(run_cli, bz) + user = bug.creator + have_dev = _check_have_dev(bug) - origcc = bug.cc + cmd = "bugzilla modify %s " % bug.id # Test CC list and reset it email1 = "triage@lists.fedoraproject.org" - email2 = "crobinso@redhat.com" - bug.deletecc(origcc) - run_cli(cmd + "--cc %s --cc %s" % (email1, email2), bz) - bug.addcc(email1) - + run_cli(cmd + "--cc %s --cc %s" % (email1, user), bz) bug.refresh() assert email1 in bug.cc - assert email2 in bug.cc - assert len(bug.cc) == 2 + assert user in bug.cc - run_cli(cmd + "--cc=-%s" % email1, bz) + # Remove CC via command line + # Unprivileged user can only add/remove their own CC value + run_cli(cmd + "--cc=-%s" % user, bz) bug.refresh() - assert email1 not in bug.cc + assert user not in bug.cc - # Test assigned target - run_cli(cmd + "--assignee %s" % email1, bz) + # Re-add CC via API + bug.addcc(user) bug.refresh() - assert bug.assigned_to == email1 + assert user in bug.cc - # Test QA target - run_cli(cmd + "--qa_contact %s" % email1, bz) + # Remove it again, via API + bug.deletecc(user) bug.refresh() - assert bug.qa_contact == email1 + assert user not in bug.cc + assert bug.cc - # Reset values - bug.deletecc(bug.cc) - run_cli(cmd + "--reset-qa-contact --reset-assignee", bz) + perm_error = "required permissions may change that field" - bug.refresh() - assert bug.cc == [] - assert bug.assigned_to == "crobinso@redhat.com" - assert bug.qa_contact == "extras-qa@fedoraproject.org" + # Test assigned and QA target + try: + run_cli(cmd + "--assignee %s --qa_contact %s" % (email1, email1), bz) + bug.refresh() + assert bug.assigned_to == email1 + assert bug.qa_contact == email1 + except RuntimeError as e: + if have_dev: + raise + assert perm_error in str(e) -def test07ModifyMultiFlags(run_cli, backends): + # Test --reset options + try: + run_cli(cmd + "--reset-qa-contact --reset-assignee", bz) + bug.refresh() + assert bug.assigned_to != email1 + assert bug.qa_contact != email1 + except RuntimeError as e: + if have_dev: + raise + assert perm_error in str(e) + + +# See also: tests/integration/rw_api_test.py::test_update_flags +def test070ModifyMultiFlags(run_cli, backends): """ Modify flags and fixed_in for 2 bugs """ bz = _open_bz(**backends) - bugid1 = "461686" - bugid2 = "461687" + bugid1 = _BugCache.get_bug(run_cli, bz).id + bugid2 = _makebug(run_cli, bz).id cmd = "bugzilla modify %s %s " % (bugid1, bugid2) def flagstr(b): @@ -345,7 +441,7 @@ def cleardict_new(b): # Set flags and confirm - setflags = "needinfo? requires_doc_text-" + setflags = "fedora_prioritized_bug? needinfo+" run_cli(cmd + " ".join([(" --flag " + f) for f in setflags.split()]), bz) @@ -354,8 +450,8 @@ def cleardict_new(b): assert flagstr(bug1) == setflags assert flagstr(bug2) == setflags - assert bug1.get_flags("needinfo")[0]["status"] == "?" - assert bug1.get_flag_status("requires_doc_text") == "-" + assert bug1.get_flags("needinfo")[0]["status"] == "+" + assert bug1.get_flag_status("fedora_prioritized_bug") == "?" # Clear flags if cleardict_new(bug1): @@ -365,6 +461,7 @@ def cleardict_new(b): bz.update_flags(bug2.id, cleardict_new(bug2)) bug2.refresh() + # pylint: disable=use-implicit-booleaness-not-comparison assert cleardict_old(bug1) == {} assert cleardict_old(bug2) == {} @@ -376,7 +473,7 @@ def cleardict_new(b): if newfix == origfix2: newfix = origfix2 + "-2" - run_cli(cmd + "--fixed_in=%s" % newfix, bz) + run_cli(cmd + "--fixed_in '%s'" % newfix, bz) bug1.refresh() bug2.refresh() @@ -384,7 +481,7 @@ def cleardict_new(b): assert bug2.fixed_in == newfix # Reset fixed_in - run_cli(cmd + "--fixed_in=\"-\"", bz) + run_cli(cmd + "--fixed_in \"-\"", bz) bug1.refresh() bug2.refresh() @@ -392,11 +489,11 @@ def cleardict_new(b): assert bug2.fixed_in == "-" -def test07ModifyMisc(run_cli, backends): - bugid = "461686" - cmd = "bugzilla modify %s " % bugid +def test071ModifyMisc(run_cli, backends): bz = _open_bz(**backends) - bug = bz.getbug(bugid) + bug = _BugCache.get_bug(run_cli, bz) + have_dev = _check_have_dev(bug) + cmd = "bugzilla modify %s " % bug.id # modify --dependson run_cli(cmd + "--dependson 123456", bz) @@ -418,40 +515,54 @@ def test07ModifyMisc(run_cli, backends): assert [] == bug.blocks # modify --keywords + origkw = bug.keywords run_cli(cmd + "--keywords +Documentation --keywords EasyFix", bz) bug.refresh() - assert ["Documentation", "EasyFix"] == bug.keywords - run_cli(cmd + "--keywords=-EasyFix --keywords=-Documentation", - bz) - bug.refresh() - assert [] == bug.keywords - - # modify --target_release - # modify --target_milestone - targetbugid = 492463 - targetbug = bz.getbug(targetbugid) - targetcmd = "bugzilla modify %s " % targetbugid - run_cli(targetcmd + - "--target_milestone beta --target_release 6.2", bz) - targetbug.refresh() - assert targetbug.target_milestone == "beta" - assert targetbug.target_release == ["6.2"] - run_cli(targetcmd + - "--target_milestone rc --target_release 6.10", bz) - targetbug.refresh() - assert targetbug.target_milestone == "rc" - assert targetbug.target_release == ["6.10"] - - # modify --priority - # modify --severity - run_cli(cmd + "--priority low --severity high", bz) - bug.refresh() - assert bug.priority == "low" - assert bug.severity == "high" - run_cli(cmd + "--priority medium --severity medium", bz) + assert set(["Documentation", "EasyFix"] + origkw) == set(bug.keywords) + run_cli(cmd + "--keywords=-EasyFix --keywords=-Documentation", bz) bug.refresh() - assert bug.priority == "medium" - assert bug.severity == "medium" + assert origkw == bug.keywords + + perm_error = "user with the required permissions" + + try: + # modify --target_release + # modify --target_milestone + targetbugid = 492463 + targetbug = bz.getbug(targetbugid) + targetcmd = "bugzilla modify %s " % targetbugid + run_cli(targetcmd + + "--target_milestone beta --target_release 6.2", bz) + targetbug.refresh() + assert targetbug.target_milestone == "beta" + assert targetbug.target_release == ["6.2"] + run_cli(targetcmd + + "--target_milestone rc --target_release 6.10", bz) + targetbug.refresh() + assert targetbug.target_milestone == "rc" + assert targetbug.target_release == ["6.10"] + except RuntimeError as e: + # As of Nov 2024 this needs even extra permissions, probably + # due to RHEL products being locked down + # if have_dev: + # raise + assert perm_error in str(e) + + try: + # modify --priority + # modify --severity + run_cli(cmd + "--priority low --severity high", bz) + bug.refresh() + assert bug.priority == "low" + assert bug.severity == "high" + run_cli(cmd + "--priority medium --severity medium", bz) + bug.refresh() + assert bug.priority == "medium" + assert bug.severity == "medium" + except RuntimeError as e: + if have_dev: + raise + assert perm_error in str(e) # modify --os # modify --platform @@ -503,7 +614,7 @@ def _test8Attachments(run_cli, backends): testfile = "../tests/data/bz-attach-get1.txt" # Add attachment as CLI option - setbug = _makebug(run_cli, bz) + setbug = _BugCache.get_bug(run_cli, bz) setbug = bz.getbug(setbug.id, extra_fields=["attachments"]) orignumattach = len(setbug.attachments) @@ -575,7 +686,7 @@ def _test8Attachments(run_cli, backends): out = run_cli(cmd + "--getall %s" % getbug.id, bz).splitlines() assert len(out) == numattach - fnames = [l.split(" ", 1)[1].strip() for l in out] + fnames = [line.split(" ", 1)[1].strip() for line in out] assert len(fnames) == numattach for f in fnames: if not os.path.exists(f): @@ -587,7 +698,7 @@ def _test8Attachments(run_cli, backends): out = run_cli(ignorecmd, bz).splitlines() assert len(out) == (numattach - 1) - fnames = [l.split(" ", 1)[1].strip() for l in out] + fnames = [line.split(" ", 1)[1].strip() for line in out] assert len(fnames) == (numattach - 1) for f in fnames: if not os.path.exists(f): @@ -597,45 +708,42 @@ def _test8Attachments(run_cli, backends): def test09Whiteboards(run_cli, backends): bz = _open_bz(**backends) - bug_id = "663674" - cmd = "bugzilla modify %s " % bug_id - bug = bz.getbug(bug_id) + bug = _BugCache.get_bug(run_cli, bz) + have_dev = _check_have_dev(bug) + cmd = "bugzilla modify %s " % bug.id # Set all whiteboards initval = str(random.randint(1, 1024)) - run_cli(cmd + - "--whiteboard =%sstatus " - "--devel_whiteboard =%sdevel " - "--internal_whiteboard '=%sinternal, security, foo security1' " - "--qa_whiteboard =%sqa " % - (initval, initval, initval, initval), bz) + statusstr = initval + "foo, bar, baz bar1" + devstr = initval + "devel" + internalstr = initval + "internal" + qastr = initval + "qa" + run_cmd = (cmd + f"--whiteboard '{statusstr}' ") + if have_dev: + run_cmd += ( + f"--devel_whiteboard '{devstr}' " + f"--internal_whiteboard '{internalstr}' " + f"--qa_whiteboard '{qastr}' ") + run_cli(run_cmd, bz) bug.refresh() - assert bug.whiteboard == (initval + "status") - assert bug.qa_whiteboard == (initval + "qa") - assert bug.devel_whiteboard == (initval + "devel") - assert (bug.internal_whiteboard == - (initval + "internal, security, foo security1")) + assert bug.whiteboard == statusstr - # Modify whiteboards - run_cli(cmd + - "--whiteboard =foobar " - "--qa_whiteboard _app " - "--devel_whiteboard =pre-%s" % bug.devel_whiteboard, bz) + if have_dev: + assert bug.qa_whiteboard == qastr + assert bug.devel_whiteboard == devstr + assert bug.internal_whiteboard == internalstr + # Remove a tag + run_cli(cmd + "--whiteboard=-bar, ", bz) bug.refresh() - assert bug.qa_whiteboard == (initval + "qa" + " _app") - assert bug.devel_whiteboard == ("pre-" + initval + "devel") - assert bug.status_whiteboard == "foobar" + statusstr = statusstr.replace("bar, ", "") + assert bug.status_whiteboard == statusstr - # Verify that tag manipulation is smart about separator - run_cli(cmd + - "--qa_whiteboard=-_app " - "--internal_whiteboard=-security,", bz) + run_cli(cmd + "--whiteboard NEWBIT", bz) bug.refresh() - - assert bug.qa_whiteboard == (initval + "qa") - assert bug.internal_whiteboard == (initval + "internal, foo security1") + statusstr += " NEWBIT" + assert bug.whiteboard == statusstr # Clear whiteboards update = bz.build_update( @@ -645,9 +753,10 @@ def test09Whiteboards(run_cli, backends): bug.refresh() assert bug.whiteboard == "" - assert bug.qa_whiteboard == "" - assert bug.devel_whiteboard == "" - assert bug.internal_whiteboard == "" + if have_dev: + assert bug.qa_whiteboard == "" + assert bug.devel_whiteboard == "" + assert bug.internal_whiteboard == "" def test10Login(run_cli, monkeypatch): @@ -698,8 +807,8 @@ def test11UserUpdate(backends): # Test group_get try: - group = bz.getgroup("fedora_contrib") - group.refresh() + groupobj = bz.getgroup(group) + groupobj.refresh() except Exception as e: if have_admin: raise @@ -749,6 +858,15 @@ def test11UserUpdate(backends): user.refresh() assert user.groupnames == origgroups + # Try user create + try: + name = "pythonbugzilla-%s" % datetime.datetime.today() + bz.createuser(name + "@example.com", name, name) + except Exception as e: + if have_admin: + raise + assert "Sorry, you aren't a member" in str(e) + def test11ComponentEditing(backends): bz = _open_bz(**backends) @@ -800,10 +918,6 @@ def compare(data, newid): # bugzilla 5 error string ("You are not allowed" in str(e))) - # bugzilla.redhat.com doesn't have REST editcomponent yet - tests.utils.skip_if_rest( - bz, "editcomponent not supported for redhat REST API") - # Edit component data = basedata.copy() data.update({ @@ -819,40 +933,39 @@ def compare(data, newid): if newid is not None: compare(data, newid) except Exception as e: - if have_admin: + if bz.is_rest(): + # redhat REST does not support component editing + assert "A REST API resource was not found" in str(e) + elif have_admin: raise - assert (("Sorry, you aren't a member" in str(e)) or - # bugzilla 5 error string - ("You are not allowed" in str(e))) + else: + assert (("Sorry, you aren't a member" in str(e)) or + # bugzilla 5 error string + ("You are not allowed" in str(e))) -def test13SubComponents(backends): +def test13SubComponents(run_cli, backends): bz = _open_bz(**backends) - # Long closed RHEL5 lvm2 bug. This component has sub_components - bug = bz.getbug("185526") + bug = _make_subcomponent_bug(run_cli, bz) + bug.autorefresh = True - assert bug.component == "lvm2" + assert bug.component == "Extensions" bz.update_bugs(bug.id, bz.build_update( - component="lvm2", sub_component="Command-line tools (RHEL5)")) + component="Extensions", sub_component="RedHat")) bug.refresh() - assert bug.sub_components == {"lvm2": ["Command-line tools (RHEL5)"]} + assert bug.sub_components == {"Extensions": ["RedHat"]} bz.update_bugs(bug.id, bz.build_update( - component="lvm2", sub_component="Default / Unclassified (RHEL5)")) + component="Extensions", sub_component="AgileTools")) bug.refresh() - assert bug.sub_components == {"lvm2": [ - "Default / Unclassified (RHEL5)"]} + assert bug.sub_components == {"Extensions": ["AgileTools"]} -def test14ExternalTrackersAddUpdateRemoveQuery(backends): - bz = _open_bz(**backends) - bugid = 461686 +def _testExternalTrackers(run_cli, bz): + bugid = _BugCache.get_bug(run_cli, bz).id ext_bug_id = 380489 - tests.utils.skip_if_rest( - bz, "unknown if REST API has externaltrackers support") - # Delete any existing external trackers to get to a known state ids = [bug['id'] for bug in bz.getbug(bugid).external_bugs] if ids != []: @@ -897,36 +1010,54 @@ def test14ExternalTrackersAddUpdateRemoveQuery(backends): assert len(ids) == 0 +def test14ExternalTrackersAddUpdateRemoveQuery(run_cli, backends): + bz = _open_bz(**backends) + try: + _testExternalTrackers(run_cli, bz) + except Exception as e: + if not bz.is_rest(): + raise + assert "No REST API available" in str(e) + + def test15EnsureLoggedIn(run_cli, backends): bz = _open_bz(**backends) comm = "bugzilla --ensure-logged-in query --bug_id 979546" run_cli(comm, bz) + # Test that we don't pollute the query dict with auth info + query = {"id": [1234567]} + origquery = query.copy() + bz.query(query) + assert query == origquery + def test16ModifyTags(run_cli, backends): - bugid = "461686" - cmd = "bugzilla modify %s " % bugid bz = _open_bz(**backends) - bug = bz.getbug(bugid) + bug = _BugCache.get_bug(run_cli, bz) + cmd = "bugzilla modify %s " % bug.id - tests.utils.skip_if_rest(bz, "update_tags not supported for REST API") + try: + if bug.tags: + bz.update_tags(bug.id, tags_remove=bug.tags) + bug.refresh() + assert bug.tags == [] - if bug.tags: - bz.update_tags(bug.id, tags_remove=bug.tags) + run_cli(cmd + "--tags foo --tags +bar --tags baz", bz) bug.refresh() - assert bug.tags == [] - - run_cli(cmd + "--tags foo --tags +bar --tags baz", bz) - bug.refresh() - assert bug.tags == ["foo", "bar", "baz"] + assert bug.tags == ["foo", "bar", "baz"] - run_cli(cmd + "--tags=-bar", bz) - bug.refresh() - assert bug.tags == ["foo", "baz"] + run_cli(cmd + "--tags=-bar", bz) + bug.refresh() + assert bug.tags == ["foo", "baz"] - bz.update_tags(bug.id, tags_remove=bug.tags) - bug.refresh() - assert bug.tags == [] + bz.update_tags(bug.id, tags_remove=bug.tags) + bug.refresh() + assert bug.tags == [] + except Exception as e: + if not bz.is_rest(): + raise + assert "No REST API available" in str(e) def test17LoginAPIKey(backends): diff --git a/tests/utils.py b/tests/utils.py index 339d7169..058d81bf 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -10,10 +10,8 @@ import shlex import sys -import pytest - +from bugzilla import Bugzilla import bugzilla._cli -from bugzilla._compatimports import IS_PY3 import tests @@ -30,26 +28,14 @@ def tests_path(filename): return filename -def fake_stream(text): - if IS_PY3: - return io.StringIO(text) - else: - return io.BytesIO(text) - - def monkeypatch_getpass(monkeypatch): - # pylint: disable=undefined-variable - if IS_PY3: - use_input = input # noqa - else: - use_input = raw_input # noqa - monkeypatch.setattr(getpass, "getpass", use_input) + monkeypatch.setattr(getpass, "getpass", input) def sanitize_json(rawout): # py2.7 leaves trailing whitespace after commas. strip it so # tests pass on both python versions - return "\n".join([l.rstrip() for l in rawout.splitlines()]) + return "\n".join([line.rstrip() for line in rawout.splitlines()]) def open_functional_bz(bzclass, url, kwargs): @@ -60,22 +46,13 @@ def open_functional_bz(bzclass, url, kwargs): if kwargs.get("force_xmlrpc", False): assert bz.is_xmlrpc() is True - # Set a session timeout of 30 seconds - session = bz.get_requests_session() - origrequest = session.request - - def fake_request(*args, **kwargs): - if "timeout" not in kwargs: - kwargs["timeout"] = 60 - return origrequest(*args, **kwargs) - - session.request = fake_request + # Set a request timeout of 60 seconds + os.environ["PYTHONBUGZILLA_REQUESTS_TIMEOUT"] = "60" return bz -def skip_if_rest(bz, msg): - if bz.is_rest(): - pytest.skip(msg) +def open_bz(url, bzclass=Bugzilla, **kwargs): + return open_functional_bz(bzclass=bzclass, url=url, kwargs=kwargs) def diff_compare(inputdata, filename, expect_out=None): @@ -118,7 +95,7 @@ def do_run_cli(capsys, monkeypatch, argv = shlex.split(argvstr) monkeypatch.setattr(sys, "argv", argv) if stdin: - monkeypatch.setattr(sys, "stdin", fake_stream(stdin)) + monkeypatch.setattr(sys, "stdin", io.StringIO(stdin)) else: monkeypatch.setattr(sys.stdin, "isatty", lambda: True) diff --git a/tox.ini b/tox.ini index 7fa6266b..0d51036b 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27,py34,py35,py36,py37,py38 +envlist = py34,py35,py36,py37,py38,py39,py310 [testenv] deps =