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 e300acb9..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: [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,36 +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-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 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 90f5ba77..f3c91ab1 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,32 @@ # 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` 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 7d746c3f..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: - msg = "cookiefile=%s not in Mozilla format" % cookiefile - raise BugzillaError(msg) from None - - 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 43a503f6..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,24 +26,38 @@ 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 + 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 @@ -52,15 +66,20 @@ def _op(self, method, apiurl, paramdict=None): log.debug("Bugzilla REST %s %s params=%s", method, fullurl, paramdict) data = None - params = None + authparams = self._bugzillasession.get_auth_params() if method == "GET": - params = paramdict + authparams.update(paramdict or {}) else: data = json.dumps(paramdict or {}) - response = self._bugzillasession.request(method, fullurl, data=data, - params=params) - 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 db905541..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 @@ -44,18 +44,11 @@ def __request_helper(self, url, 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 @@ -82,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 @@ -125,22 +118,14 @@ def _ServerProxy__request(self, methodname, params): newparams = params and params[0].copy() or {} log.debug("XMLRPC call: %s(%s)", methodname, newparams) - api_key = self.__bugzillasession.get_api_key() - token_value = self.__bugzillasession.get_token_value() - - if api_key is not None: - if 'Bugzilla_api_key' not in newparams: - newparams['Bugzilla_api_key'] = api_key - elif token_value is not None: - if 'Bugzilla_token' not in newparams: - newparams['Bugzilla_token'] = token_value + authparams = self.__bugzillasession.get_auth_params() + authparams.update(newparams) # pylint: disable=no-member - ret = ServerProxy._ServerProxy__request(self, methodname, (newparams,)) + 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 84d516be..02d6a367 100755 --- a/bugzilla/_cli.py +++ b/bugzilla/_cli.py @@ -21,11 +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 DEFAULT_BZ = 'https://bugzilla.redhat.com' @@ -124,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") @@ -185,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") @@ -256,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) @@ -401,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]]' @@ -436,15 +445,28 @@ def setup_parser(): # Command routines # #################### -def _merge_field_opts(query, fields, parser): +def _merge_field_opts(query, fields, field_jsons, parser): + values = {} + # Add any custom fields if specified - for f in fields: + 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 = {} @@ -599,8 +621,7 @@ def _do_query(bz, opt, parser): kwopts["tags"] = opt.tags built_query = bz.build_query(**kwopts) - if opt.fields: - _merge_field_opts(built_query, opt.fields, parser) + _merge_field_opts(built_query, opt.fields, opt.field_jsons, parser) built_query.update(q) q = built_query @@ -661,7 +682,7 @@ def _filter_components(compdetails): elif opt.component_owners: details = bz.getcomponentsdetails(productname) for c in sorted(_filter_components(details)): - print(u"%s: %s" % (c, details[c]['default_assigned_to'])) + print("%s: %s" % (c, details[c]['default_assigned_to'])) def _convert_to_outputformat(output): @@ -907,8 +928,7 @@ def parse_multi(val): kwopts["comment_private"] = opt.private ret = bz.build_createbug(**kwopts) - if opt.fields: - _merge_field_opts(ret, opt.fields, parser) + _merge_field_opts(ret, opt.fields, opt.field_jsons, parser) b = bz.createbug(ret) b.refresh() @@ -1047,10 +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] - if opt.fields: - _merge_field_opts(update, opt.fields, 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) @@ -1158,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) @@ -1212,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: @@ -1293,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: @@ -1307,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 47fcaf79..00000000 --- a/bugzilla/_compatimports.py +++ /dev/null @@ -1,11 +0,0 @@ -# This work is licensed under the GNU GPLv2 or later. -# See the COPYING file in the top-level directory. - -# pylint: disable=unused-import - -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) diff --git a/bugzilla/_session.py b/bugzilla/_session.py index 26ff8684..ff00f3f5 100644 --- a/bugzilla/_session.py +++ b/bugzilla/_session.py @@ -4,10 +4,12 @@ from logging import getLogger import os -import requests +import sys +import urllib.parse -from ._compatimports import urlparse +import requests +from .exceptions import BugzillaHTTPError log = getLogger(__name__) @@ -17,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 @@ -36,13 +40,14 @@ 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 @@ -52,35 +57,38 @@ def _get_timeout(self): 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 @@ -89,4 +97,25 @@ def request(self, *args, **kwargs): timeout = self._get_timeout() if "timeout" not in kwargs: kwargs["timeout"] = timeout - return self._session.request(*args, **kwargs) + + 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/apiversion.py b/bugzilla/apiversion.py index 34b7f6f0..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 = "3.0.0" +version = "3.3.0" __version__ = version diff --git a/bugzilla/base.py b/bugzilla/base.py index 924b230a..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 @@ -364,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() @@ -511,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) @@ -530,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 @@ -614,6 +612,8 @@ 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) @@ -671,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() @@ -1078,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() @@ -1096,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: @@ -1193,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. @@ -1226,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), @@ -1297,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) @@ -1324,9 +1340,23 @@ def query(self, query): "appear to support API queries derived from bugzilla " "web URL queries." % e) from None - log.debug("Query returned %s bugs", len(r['bugs'])) - return [Bug(self, dict=b, - autorefresh=self.bug_autorefresh) for b in r['bugs']] + 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] + + 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): """ @@ -1484,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 @@ -1534,10 +1558,8 @@ def c(val): 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) @@ -1744,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) @@ -1794,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 @@ -1984,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. @@ -2027,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. @@ -2072,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 5faa28b8..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,6 +6,7 @@ import copy from logging import getLogger +from urllib.parse import urlparse, urlunparse log = getLogger(__name__) @@ -41,8 +40,16 @@ 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): """ @@ -138,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): @@ -285,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 # ##################### @@ -353,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 8b2c90b7..021d70fb 100644 --- a/examples/apikey.py +++ b/examples/apikey.py @@ -20,6 +20,6 @@ # 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 09c1c533..a8aa6728 100644 --- a/examples/bug_autorefresh.py +++ b/examples/bug_autorefresh.py @@ -9,7 +9,7 @@ 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 be1c75dc..124a93b0 100644 --- a/examples/create.py +++ b/examples/create.py @@ -12,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 866b0d6d..f164c0a1 100644 --- a/examples/getbug.py +++ b/examples/getbug.py @@ -11,14 +11,14 @@ 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) @@ -33,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 94723884..1cb4e797 100644 --- a/examples/getbug_restapi.py +++ b/examples/getbug_restapi.py @@ -10,7 +10,7 @@ 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 ac285118..05ad7026 100644 --- a/examples/query.py +++ b/examples/query.py @@ -10,7 +10,7 @@ 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) @@ -55,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 @@ -65,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 ff13bdf3..86b1967c 100644 --- a/examples/update.py +++ b/examples/update.py @@ -10,7 +10,7 @@ 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) @@ -23,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)) @@ -38,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()) @@ -46,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 08c14dab..09ff4132 100644 --- a/man/bugzilla.rst +++ b/man/bugzilla.rst @@ -127,18 +127,10 @@ they expire the tool errors, rather than subtly change output. **Syntax:** ``--no-cache-credentials`` -Don't save any bugzilla cookies or tokens to disk, and don't use any +Don't save any bugzilla tokens to disk, and don't use any pre-existing credentials. -``--cookiefile`` -^^^^^^^^^^^^^^^^ - -**Syntax:** ``--cookiefile`` COOKIEFILE - -cookie file to use for bugzilla authentication - - ``--tokenfile`` ^^^^^^^^^^^^^^^ @@ -363,8 +355,8 @@ Bug assignee QA contact -``--flag`` -^^^^^^^^^^ +``-f, --flag`` +^^^^^^^^^^^^^^ **Syntax:** ``--flag`` FLAG @@ -424,13 +416,22 @@ RHBZ 'Fixed in version' field ``--field`` ^^^^^^^^^^^ -**Syntax:** ``--field`` FIELD`` VALUE +**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 ============== @@ -439,8 +440,8 @@ These options are shared by several commands, for tweaking the text output of the command results. -``-f, --full`` -^^^^^^^^^^^^^^ +``--full`` +^^^^^^^^^^ **Syntax:** ``--full`` @@ -790,6 +791,46 @@ List the versions for the given product 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 ================================= @@ -801,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 @@ -867,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 cb79ddfd..716fc933 100644 --- a/python-bugzilla.spec +++ b/python-bugzilla.spec @@ -1,5 +1,5 @@ Name: python-bugzilla -Version: 3.0.0 +Version: 3.3.0 Release: 1%{?dist} Summary: Python library for interacting with Bugzilla @@ -9,7 +9,6 @@ Source0: https://github.com/python-bugzilla/python-bugzilla/archive/v%{ve BuildArch: noarch BuildRequires: python3-devel -BuildRequires: python3-docutils BuildRequires: python3-requests BuildRequires: python3-setuptools BuildRequires: python3-pytest @@ -49,14 +48,6 @@ This package includes the 'bugzilla' command-line tool for interacting with bugz %install %{__python3} setup.py install -O1 --root %{buildroot} -# 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 -%global python_env_path %{__python3} -for f in $(find %{buildroot} -type f -executable -print); do - sed -i "1 s|^#!/usr/bin/.*|#!%{python_env_path}|" $f || : -done - %check 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 50f2b522..b9db594d 100755 --- a/setup.py +++ b/setup.py @@ -2,12 +2,11 @@ import glob import os +import shutil import subprocess import sys -import distutils.command.build -from distutils.core import Command -from setuptools import setup +import setuptools def get_version(): @@ -17,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): @@ -52,7 +51,7 @@ def run(self): 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 = [] @@ -75,14 +74,21 @@ def run(self): 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) @@ -99,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): @@ -111,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', @@ -125,22 +130,22 @@ def _parse_requirements(fname): 'Operating System :: OS Independent', 'Programming Language :: Python', '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 0ea42d39..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,13 +44,23 @@ 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") + 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" + 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: @@ -52,12 +68,16 @@ def pytest_ignore_collect(path, config): 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") @@ -103,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_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 index 972c2765..b21a6ee2 100644 --- a/tests/data/mockargs/test_modify5.txt +++ b/tests/data/mockargs/test_modify5.txt @@ -2,13 +2,15 @@ {'alias': 'fooalias', 'assigned_to': 'foo@example.com', 'bar': 'foo', - 'blocks': {'add': [1234], 'remove': [1235], 'set': []}, + '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': []}, + 'depends_on': {'add': ['2234'], 'remove': ['2235'], 'set': []}, 'groups': {'add': ['foogroup']}, 'keywords': {'add': ['newkeyword'], 'remove': ['byekeyword'], 'set': []}, 'minor_update': True, diff --git a/tests/data/mockargs/test_new2.txt b/tests/data/mockargs/test_new2.txt index 5bd147e2..a3636ead 100644 --- a/tests/data/mockargs/test_new2.txt +++ b/tests/data/mockargs/test_new2.txt @@ -2,6 +2,8 @@ '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', 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 00318613..fcd6fbd8 100644 --- a/tests/test_api_authfiles.py +++ b/tests/test_api_authfiles.py @@ -13,48 +13,20 @@ import shutil import tempfile -import pytest -import requests - -import bugzilla - import tests import tests.mockbackend import tests.utils -def testCookies(monkeypatch): - monkeypatch.setitem(os.environ, "HOME", - os.path.dirname(__file__) + "/data/homedir") - +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 @@ -124,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: @@ -150,42 +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([ - line for line in open(f).readlines() if - not line.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 @@ -205,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 2f762453..23448d6b 100644 --- a/tests/test_api_bug.py +++ b/tests/test_api_bug.py @@ -94,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, @@ -106,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 @@ -161,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", @@ -177,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 @@ -184,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 2975d106..584858f1 100644 --- a/tests/test_cli_attach.py +++ b/tests/test_cli_attach.py @@ -49,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* @@ -58,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 2955a82e..5a898bef 100644 --- a/tests/test_cli_misc.py +++ b/tests/test_cli_misc.py @@ -12,12 +12,12 @@ 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 @@ -38,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", @@ -111,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 ed5519c3..e3731451 100644 --- a/tests/test_cli_modify.py +++ b/tests/test_cli_modify.py @@ -85,6 +85,7 @@ def test_modify(run_cli): 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", diff --git a/tests/test_cli_new.py b/tests/test_cli_new.py index 5e4742be..86e153ab 100644 --- a/tests/test_cli_new.py +++ b/tests/test_cli_new.py @@ -41,6 +41,7 @@ def test_new(run_cli): 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", diff --git a/tests/test_cli_query.py b/tests/test_cli_query.py index 98c6028b..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", diff --git a/tests/test_ro_functional.py b/tests/test_ro_functional.py index 4c3ec476..6bb770da 100644 --- a/tests/test_ro_functional.py +++ b/tests/test_ro_functional.py @@ -8,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 @@ -47,6 +51,7 @@ class MyBugzilla(bugzilla.Bugzilla): 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") @@ -57,10 +62,49 @@ def test_rest_xmlrpc_detection(): # 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) ################### @@ -101,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) @@ -108,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) @@ -115,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) @@ -122,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) @@ -130,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) @@ -150,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) @@ -158,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) @@ -166,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) @@ -182,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) @@ -191,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) @@ -209,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 @@ -227,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): @@ -249,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 @@ -258,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 @@ -268,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' " @@ -280,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) @@ -289,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) @@ -310,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) @@ -333,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) @@ -344,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) @@ -364,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 6bd62d31..600fa7ed 100644 --- a/tests/test_rw_functional.py +++ b/tests/test_rw_functional.py @@ -22,9 +22,13 @@ 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(",")] @@ -50,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) @@ -81,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 --minor-update" % 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() @@ -171,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" @@ -222,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) @@ -237,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) @@ -244,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) @@ -253,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 = _BugCache.get_bug(run_cli, bz) + user = bug.creator + have_dev = _check_have_dev(bug) - bug = bz.getbug(bugid) - - 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): @@ -343,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) @@ -352,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): @@ -363,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) == {} @@ -374,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() @@ -382,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() @@ -390,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) @@ -416,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) + assert set(["Documentation", "EasyFix"] + origkw) == set(bug.keywords) + run_cli(cmd + "--keywords=-EasyFix --keywords=-Documentation", 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" + 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 @@ -501,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) @@ -595,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( @@ -643,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): @@ -696,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 @@ -747,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) @@ -798,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({ @@ -817,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 != []: @@ -895,6 +1010,16 @@ 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" @@ -908,29 +1033,31 @@ def test15EnsureLoggedIn(run_cli, backends): 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 a9dd98dc..058d81bf 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -10,8 +10,7 @@ import shlex import sys -import pytest - +from bugzilla import Bugzilla import bugzilla._cli import tests @@ -52,9 +51,8 @@ def open_functional_bz(bzclass, url, kwargs): 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): diff --git a/tox.ini b/tox.ini index 1a6ced78..0d51036b 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py34,py35,py36,py37,py38,py39 +envlist = py34,py35,py36,py37,py38,py39,py310 [testenv] deps =