12Parker/python-migrations
092
1{"repo": "godaddy/tartufo", "pull_number": 19, "instance_id": "godaddy__tartufo-19", "issue_numbers": "", "base_commit": "db990f3a75a995d7936a343b8d3e0928ed2e38a5", "patch": "diff --git a/setup.py b/setup.py\n--- a/setup.py\n+++ b/setup.py\n@@ -2,8 +2,11 @@\n from setuptools import setup\n \n INSTALL_REQUIRES = [\n- 'GitPython == 2.1.1',\n- 'truffleHogRegexes == 0.0.7',\n+ \"click >= 7.0.0, < 8.0.0\",\n+ \"GitPython >= 2.1.1, < 4.0.0\",\n+ \"pathlib2; python_version < '3.4'\",\n+ \"toml >= 0.10.0, < 1.0.0\",\n+ \"truffleHogRegexes >= 0.0.7, < 1.0.0\",\n \"typing; python_version < '3.5'\",\n ]\n \ndiff --git a/tartufo/__main__.py b/tartufo/__main__.py\n--- a/tartufo/__main__.py\n+++ b/tartufo/__main__.py\n@@ -1,7 +1,5 @@\n-import sys\n-\n from tartufo import cli\n \n \n if __name__ == \"__main__\":\n- cli.main(sys.argv[1:])\n+ cli.main() # pylint: disable=no-value-for-parameter\ndiff --git a/tartufo/cli.py b/tartufo/cli.py\n--- a/tartufo/cli.py\n+++ b/tartufo/cli.py\n@@ -1,200 +1,142 @@\n # -*- coding: utf-8 -*-\n \n-import argparse\n import re\n-from typing import List, Optional\n+from functools import partial\n+from typing import cast, TextIO\n \n+import click\n import truffleHogRegexes.regexChecks\n \n from tartufo import config, scanner, util\n \n \n-def main(argv=None):\n- # type: (Optional[List[str]]) -> int\n- args = parse_args(argv)\n+err = partial(click.secho, fg=\"red\", bold=True, err=True) # pylint: disable=invalid-name\n \n- if not (args.do_entropy or args.do_regex):\n- raise RuntimeError(\"no analysis requested\")\n \n- rules_regexes = config.configure_regexes_from_args(args, truffleHogRegexes.regexChecks.regexes)\n+@click.command(name=\"tartufo\", # noqa: C901\n+ context_settings=dict(help_option_names=[\"-h\", \"--help\"]))\n+@click.option(\"--json/--no-json\", help=\"Output in JSON format.\", is_flag=True)\n+@click.option(\"--rules\", multiple=True, type=click.File(\"r\"),\n+ help=\"Path(s) to regex rules json list file(s).\")\n+@click.option(\"--default-regexes/--no-default-regexes\", is_flag=True, default=True,\n+ help=\"Whether to include the default regex list when configuring\"\n+ \" search patterns. Only applicable if --rules is also specified.\"\n+ \" [default: --default-regexes]\")\n+@click.option(\"--entropy/--no-entropy\", is_flag=True, default=True,\n+ help=\"Enable entropy checks. [default: True]\")\n+@click.option(\"--regex/--no-regex\", is_flag=True, default=False,\n+ help=\"Enable high signal regexes checks. [default: False]\")\n+@click.option(\"--since-commit\", help=\"Only scan from a given commit hash.\")\n+@click.option(\"--max-depth\", default=1000000,\n+ help=\"The max commit depth to go back when searching for secrets.\"\n+ \" [default: 1000000]\")\n+@click.option(\"--branch\", help=\"Specify a branch name to scan only that branch.\")\n+@click.option(\"-i\", \"--include-paths\", type=click.File(\"r\"),\n+ help=\"File with regular expressions (one per line), at least one of \"\n+ \"which must match a Git object path in order for it to be scanned; \"\n+ \"lines starting with '#' are treated as comments and are ignored. \"\n+ \"If empty or not provided (default), all Git object paths are \"\n+ \"included unless otherwise excluded via the --exclude-paths option.\")\n+@click.option(\"-x\", \"--exclude-paths\", type=click.File(\"r\"),\n+ help=\"File with regular expressions (one per line), none of which may \"\n+ \"match a Git object path in order for it to be scanned; lines \"\n+ \"starting with '#' are treated as comments and are ignored. If \"\n+ \"empty or not provided (default), no Git object paths are excluded \"\n+ \"unless effectively excluded via the --include-paths option.\")\n+@click.option(\"--repo-path\",\n+ type=click.Path(\n+ exists=True,\n+ file_okay=False,\n+ resolve_path=True,\n+ allow_dash=False\n+ ),\n+ help=\"Path to local repo clone. If provided, git_url will not be used.\")\n+@click.option(\"--cleanup/--no-cleanup\", is_flag=True, default=False,\n+ help=\"Clean up all temporary result files. [default: False]\")\n+@click.option(\"--pre-commit\", is_flag=True, default=False,\n+ help=\"Scan staged files in local repo clone.\")\n+@click.option(\"--config\",\n+ type=click.File(mode='r'),\n+ is_eager=True,\n+ callback=config.read_pyproject_toml,\n+ help=\"Read configuration from specified file. [default: pyproject.toml]\")\n+@click.argument(\"git_url\", required=False)\n+@click.pass_context\n+def main(ctx, **kwargs):\n+ # type: (click.Context, config.OptionTypes) -> None\n+ \"\"\"Find secrets hidden in the depths of git.\n+\n+ Tartufo will, by default, scan the entire history of a git repository\n+ for any text which looks like a secret, password, credential, etc. It can\n+ also be made to work in pre-commit mode, for scanning blobs of text as a\n+ pre-commit hook.\n+ \"\"\"\n+ if not any((kwargs[\"entropy\"], kwargs[\"regex\"])):\n+ err(\"No analysis requested.\")\n+ ctx.exit(1)\n+ if not any((kwargs[\"pre_commit\"], kwargs[\"repo_path\"], kwargs[\"git_url\"])):\n+ err(\"You must specify one of --pre-commit, --repo-path, or git_url.\")\n+ ctx.exit(1)\n+ try:\n+ rules_regexes = config.configure_regexes_from_args(\n+ kwargs,\n+ truffleHogRegexes.regexChecks.regexes\n+ )\n+ except ValueError as exc:\n+ err(str(exc))\n+ ctx.exit(1)\n+ if kwargs[\"regex\"] and not rules_regexes:\n+ err(\"Regex checks requested, but no regexes found.\")\n+ ctx.exit(1)\n \n # read & compile path inclusion/exclusion patterns\n path_inclusions = []\n path_exclusions = []\n- if args.include_paths:\n- for pattern in set(l[:-1].lstrip() for l in args.include_paths):\n+ paths_file = cast(TextIO, kwargs[\"include_paths\"])\n+ if paths_file:\n+ for pattern in [l[:-1].lstrip() for l in paths_file]:\n if pattern and not pattern.startswith(\"#\"):\n path_inclusions.append(re.compile(pattern))\n- if args.exclude_paths:\n- for pattern in set(l[:-1].lstrip() for l in args.exclude_paths):\n+ paths_file = cast(TextIO, kwargs[\"exclude_paths\"])\n+ if paths_file:\n+ for pattern in [l[:-1].lstrip() for l in paths_file]:\n if pattern and not pattern.startswith(\"#\"):\n path_exclusions.append(re.compile(pattern))\n \n- if args.pre_commit:\n+ if kwargs[\"pre_commit\"]:\n output = scanner.find_staged(\n- args.repo_path,\n- args.output_json,\n- args.do_regex,\n- args.do_entropy,\n+ cast(str, kwargs[\"repo_path\"]),\n+ cast(bool, kwargs[\"json\"]),\n+ cast(bool, kwargs[\"regex\"]),\n+ cast(bool, kwargs[\"entropy\"]),\n custom_regexes=rules_regexes,\n suppress_output=False,\n path_inclusions=path_inclusions,\n path_exclusions=path_exclusions,\n )\n else:\n- if args.repo_path is None and args.git_url is None:\n- print(\"ERROR: One of git_url or --repo_path is required\")\n- return 1\n output = scanner.find_strings(\n- args.git_url,\n- args.since_commit,\n- args.max_depth,\n- args.output_json,\n- args.do_regex,\n- args.do_entropy,\n+ cast(str, kwargs[\"git_url\"]),\n+ cast(str, kwargs[\"since_commit\"]),\n+ cast(int, kwargs[\"max_depth\"]),\n+ cast(bool, kwargs[\"json\"]),\n+ cast(bool, kwargs[\"regex\"]),\n+ cast(bool, kwargs[\"entropy\"]),\n custom_regexes=rules_regexes,\n suppress_output=False,\n- branch=args.branch,\n- repo_path=args.repo_path,\n+ branch=cast(str, kwargs[\"branch\"]),\n+ repo_path=cast(str, kwargs[\"repo_path\"]),\n path_inclusions=path_inclusions,\n path_exclusions=path_exclusions,\n )\n- if args.cleanup:\n+\n+ if kwargs[\"cleanup\"]:\n util.clean_outputs(output)\n else:\n issues_path = output.get(\"issues_path\", None)\n if issues_path:\n print(\"Results have been saved in {}\".format(issues_path))\n \n- if output[\"found_issues\"]:\n- return 1\n- return 0\n-\n-\n-def parse_args(argv=None):\n- # type: (Optional[List[str]]) -> argparse.Namespace\n- parser = argparse.ArgumentParser(\n- description=\"Find secrets hidden in the depths of git.\"\n- )\n- parser.add_argument(\n- \"--json\", dest=\"output_json\", action=\"store_true\", help=\"Output in JSON\"\n- )\n- parser.add_argument(\n- \"--git-rules-repo\",\n- dest=\"git_rules_repo\",\n- help=\"Git repo for externally-sourced rules\",\n- )\n- parser.add_argument(\n- \"--git-rules\",\n- dest=\"git_rules_filenames\",\n- nargs=\"+\",\n- default=[],\n- action=\"append\",\n- help=\"Git-relative path(s) to regex rules json list file(s)\",\n- )\n- parser.add_argument(\n- \"--rules\",\n- dest=\"rules_filenames\",\n- nargs=\"+\",\n- default=[],\n- action=\"append\",\n- help=\"Path(s) to regex rules json list file(s)\",\n- )\n- parser.add_argument(\n- \"--default-regexes\",\n- dest=\"do_default_regexes\",\n- metavar=\"BOOLEAN\",\n- nargs=\"?\",\n- default=\"True\",\n- const=\"True\",\n- help=\"If set to one of (no, n, false, f, n, 0) and --rules or --git-rules is also specified, ignore default\"\n- \"regexes, otherwise the regexes from the rules files will be appended to the default regexes\",\n- )\n- parser.add_argument(\n- \"--entropy\",\n- dest=\"do_entropy\",\n- metavar=\"BOOLEAN\",\n- nargs=\"?\",\n- default=\"True\",\n- const=\"True\",\n- help=\"Enable entropy checks [default: True]\",\n- )\n- parser.add_argument(\n- \"--regex\",\n- dest=\"do_regex\",\n- metavar=\"BOOLEAN\",\n- nargs=\"?\",\n- default=\"False\",\n- const=\"True\",\n- help=\"Enable high signal regex checks [default: False]\",\n- )\n- parser.add_argument(\n- \"--since_commit\",\n- dest=\"since_commit\",\n- default=None,\n- help=\"Only scan from a given commit hash\",\n- )\n- parser.add_argument(\n- \"--max_depth\",\n- dest=\"max_depth\",\n- default=1000000,\n- help=\"The max commit depth to go back when searching for \" \"secrets\",\n- )\n- parser.add_argument(\n- \"--branch\", dest=\"branch\", default=None, help=\"Name of the branch to be scanned\"\n- )\n- parser.add_argument(\n- \"-i\",\n- \"--include_paths\",\n- type=argparse.FileType(\"r\"),\n- metavar=\"INCLUDE_PATHS_FILE\",\n- help=\"File with regular expressions (one per line), at least one of which must match a Git \"\n- 'object path in order for it to be scanned; lines starting with \"#\" are treated as '\n- \"comments and are ignored. If empty or not provided (default), all Git object paths are \"\n- \"included unless otherwise excluded via the --exclude_paths option.\",\n- )\n- parser.add_argument(\n- \"-x\",\n- \"--exclude_paths\",\n- type=argparse.FileType(\"r\"),\n- metavar=\"EXCLUDE_PATHS_FILE\",\n- help=\"File with regular expressions (one per line), none of which may match a Git object path \"\n- 'in order for it to be scanned; lines starting with \"#\" are treated as comments and are '\n- \"ignored. If empty or not provided (default), no Git object paths are excluded unless \"\n- \"effectively excluded via the --include_paths option.\",\n- )\n- parser.add_argument(\n- \"--repo_path\",\n- type=str,\n- dest=\"repo_path\",\n- default=None,\n- help=\"Path to local repo clone. If provided, git_url will not be used\",\n- )\n- parser.add_argument(\n- \"--cleanup\",\n- dest=\"cleanup\",\n- action=\"store_true\",\n- help=\"Clean up all temporary result files\",\n- )\n- parser.add_argument(\n- \"git_url\", nargs=\"?\", type=str, help=\"repository URL for secret searching\"\n- )\n- parser.add_argument(\n- \"--pre_commit\",\n- dest=\"pre_commit\",\n- action=\"store_true\",\n- help=\"Scan staged files in local repo clone\",\n- )\n-\n- args = parser.parse_args(argv)\n-\n- # rules_filenames and git_rules_filenames will be generated as a list of lists, they need to be flattened\n- filename_lists = args.rules_filenames\n- args.rules_filenames = [filename for filenames in filename_lists for filename in filenames]\n- filename_lists = args.git_rules_filenames\n- args.git_rules_filenames = [filename for filenames in filename_lists for filename in filenames]\n-\n- args.do_entropy = util.str2bool(args.do_entropy)\n- args.do_regex = util.str2bool(args.do_regex)\n- args.do_default_regexes = util.str2bool(args.do_default_regexes)\n- return args\n+ if output.get(\"found_issues\", False):\n+ ctx.exit(1)\n+ ctx.exit(0)\ndiff --git a/tartufo/config.py b/tartufo/config.py\n--- a/tartufo/config.py\n+++ b/tartufo/config.py\n@@ -1,59 +1,98 @@\n import argparse # pylint: disable=unused-import\n import json\n-import os\n import re\n import shutil\n-from typing import Dict, List, Pattern, Union\n+from functools import partial\n+from typing import cast, Dict, List, Optional, Pattern, TextIO, Tuple, Union\n \n+import click\n+import toml\n from tartufo import util\n \n+try:\n+ import pathlib\n+except ImportError:\n+ import pathlib2 as pathlib # type: ignore\n \n+\n+err = partial(click.secho, fg=\"red\", bold=True, err=True) # pylint: disable=invalid-name\n+OptionTypes = Union[str, int, bool, None, TextIO, Tuple[TextIO, ...]]\n+OptionsDict = Dict[str, OptionTypes]\n PatternDict = Dict[str, Union[str, Pattern]]\n \n \n-def configure_regexes_from_args(args, default_regexes):\n- # type: (argparse.Namespace, PatternDict) -> PatternDict\n- if args.do_regex:\n- if args.rules_filenames or (args.git_rules_repo and args.git_rules):\n- rules_regexes = dict(default_regexes) if args.do_default_regexes else {}\n- if args.git_rules_repo and args.git_rules:\n- configure_regexes_from_git(args.git_rules_repo, args.git_rules, rules_regexes)\n- if args.rules_filenames:\n- configure_regexes_from_rules_files(args.rules_filenames, rules_regexes)\n- return rules_regexes\n+def read_pyproject_toml(ctx, _param, value):\n+ # type: (click.Context, click.Parameter, Union[str, TextIO]) -> Optional[str]\n+ if not value:\n+ root_path = ctx.params.get(\"repo_path\", None)\n+ if not root_path:\n+ root_path = \".\"\n+ root_path = pathlib.Path(root_path).resolve()\n+ config_path = root_path / \"pyproject.toml\"\n+ if config_path.is_file():\n+ value = str(config_path)\n+ else:\n+ return None\n+ try:\n+ toml_file = toml.load(value)\n+ config = toml_file.get(\"tool\", {}).get(\"tartufo\", {})\n+ except (toml.TomlDecodeError, OSError) as exc:\n+ raise click.FileError(\n+ filename=str(config_path),\n+ hint=\"Error reading configuration file: {}\".format(exc)\n+ )\n+ if not config:\n+ return None\n+ if ctx.default_map is None:\n+ ctx.default_map = {}\n+ ctx.default_map.update( # type: ignore\n+ {k.replace(\"--\", \"\").replace(\"-\", \"_\"): v for k, v in config.items()}\n+ )\n+ return str(value)\n+\n \n- return dict(default_regexes)\n- return {}\n+def configure_regexes_from_args(args, default_regexes):\n+ # type: (OptionsDict, PatternDict) -> PatternDict\n+ regexes = {}\n+ if args[\"regex\"]:\n+ if args[\"default_regexes\"]:\n+ regexes.update(default_regexes)\n+ # FIXME: git_rules(_repo) functionality was never called, nor tested.\n+ # https://github.com/godaddy/tartufo/issues/17 added for a new feature\n+ rules_files = cast(Tuple[TextIO, ...], args[\"rules\"])\n+ if rules_files: # or (args.git_rules_repo and args.git_rules):\n+ # if args.git_rules_repo and args.git_rules:\n+ # configure_regexes_from_git(args.git_rules_repo, args.git_rules, rules_regexes)\n+ if rules_files:\n+ for rules_file in rules_files:\n+ loaded = load_rules_from_file(rules_file)\n+ dupes = set(loaded.keys()).intersection(regexes.keys())\n+ if dupes:\n+ raise ValueError(\"Rule(s) were defined multiple time: {}\".format(dupes))\n+ regexes.update(loaded)\n+ return regexes\n \n \n-def configure_regexes_from_git(git_url, repo_rules_filenames, rules_regexes):\n+def configure_regexes_from_git(git_url, repo_rules_filenames, rules_regexes): # pylint: disable=unused-argument\n # type: (str, List[str], PatternDict) -> PatternDict\n+ # FIXME: This was never called or tested.\n+ # https://github.com/godaddy/tartufo/issues/17 has been added for tracking\n rules_project_path = util.clone_git_repo(git_url)\n try:\n- rules_filenames = [os.path.join(rules_project_path, repo_rules_filename)\n- for repo_rules_filename in repo_rules_filenames]\n- return configure_regexes_from_rules_files(rules_filenames, rules_regexes)\n+ # rules_filenames = [os.path.join(rules_project_path, repo_rules_filename)\n+ # for repo_rules_filename in repo_rules_filenames]\n+ return {} # configure_regexes_from_rules_files(rules_filenames, rules_regexes)\n finally:\n shutil.rmtree(rules_project_path)\n \n \n-def configure_regexes_from_rules_files(rules_filenames, rules_regexes):\n- # type: (List[str], PatternDict) -> PatternDict\n- for rules_filename in rules_filenames:\n- load_rules_from_file(rules_filename, rules_regexes)\n-\n- return rules_regexes\n-\n-\n-def load_rules_from_file(rules_filename, rules_regexes):\n- # type: (str, PatternDict) -> None\n- # FIXME: This relies on side-effects by mutating the passed-in dictionary\n+def load_rules_from_file(rules_file):\n+ # type: (TextIO) -> Dict[str, Pattern]\n+ regexes = {}\n try:\n- with open(rules_filename, \"r\") as rules_file:\n- new_rules = json.loads(rules_file.read())\n- for rule in new_rules:\n- if rule in rules_regexes:\n- raise ValueError(\"Rule '{}' has been defined multiple times\".format(rule))\n- rules_regexes[rule] = re.compile(new_rules[rule])\n- except (IOError, ValueError) as err:\n- raise Exception(\"Error reading rules file '{}': {}\".format(rules_filename, err))\n+ new_rules = json.load(rules_file)\n+ except json.JSONDecodeError:\n+ raise ValueError(\"Error loading rules from file: {}\".format(rules_file.name))\n+ for rule in new_rules:\n+ regexes[rule] = re.compile(new_rules[rule])\n+ return regexes\n", "test_patch": "diff --git a/tests/data/exclude-files b/tests/data/exclude-files\nnew file mode 100644\n--- /dev/null\n+++ b/tests/data/exclude-files\n@@ -0,0 +1,4 @@\n+# This should be ignored\n+tests/\n+\\.venv/\n+.*\\.egg-info/\ndiff --git a/tests/data/include-files b/tests/data/include-files\nnew file mode 100644\n--- /dev/null\n+++ b/tests/data/include-files\n@@ -0,0 +1,3 @@\n+# This should be ignored.\n+tartufo/\n+scripts/\ndiff --git a/tests/test_cli.py b/tests/test_cli.py\n--- a/tests/test_cli.py\n+++ b/tests/test_cli.py\n@@ -1,105 +1,201 @@\n+import re\n import unittest\n \n+from click.testing import CliRunner\n from tartufo import cli\n \n+try:\n+ import pathlib\n+except ImportError:\n+ import pathlib2 as pathlib # type: ignore\n+\n+try:\n+ from unittest import mock\n+except ImportError:\n+ import mock # type: ignore\n+\n \n class CLITests(unittest.TestCase):\n \n- def test_main_exits_gracefully_with_empty_argv(self):\n- return_val = cli.main([])\n- self.assertEqual(return_val, 1)\n-\n- def test_parse_args_git_rules_repo(self):\n- argv = [\"--git-rules-repo\", \"git@github.test:test-owner/tartufo-test.git\"]\n- expected_git_rules_repo = \"git@github.test:test-owner/tartufo-test.git\"\n- args = cli.parse_args(argv)\n- self.assertEqual(expected_git_rules_repo, args.git_rules_repo)\n-\n- def test_parse_args_git_rules_not_specified(self):\n- argv = []\n- args = cli.parse_args(argv)\n- self.assertEqual(0, len(args.git_rules_filenames), \"args.git_rules_filenames should be empty\")\n-\n- def test_parse_args_git_rules(self):\n- argv = [\"--git-rules\", \"file1\", \"file2\"]\n- expected_rules_filenames = [\"file1\", \"file2\"]\n- args = cli.parse_args(argv)\n- self.assertEqual(\n- expected_rules_filenames, args.git_rules_filenames,\n- \"args.git_rules_filenames should be {}, is actually {}\".format(\n- expected_rules_filenames, args.rules_filenames\n+ def test_command_exits_gracefully_with_empty_argv(self):\n+ runner = CliRunner()\n+ with runner.isolated_filesystem():\n+ result = runner.invoke(cli.main)\n+ self.assertEqual(result.exit_code, 1)\n+\n+ def test_command_fails_when_no_entropy_or_regex(self):\n+ runner = CliRunner()\n+ with runner.isolated_filesystem():\n+ result = runner.invoke(cli.main, [\"--no-entropy\", \"--no-regex\"])\n+ self.assertEqual(result.output, \"No analysis requested.\\n\")\n+\n+ def test_command_fails_when_regex_requested_but_none_available(self):\n+ runner = CliRunner()\n+ with runner.isolated_filesystem():\n+ result = runner.invoke(\n+ cli.main, [\"--regex\", \"--no-default-regexes\", \"--repo-path\", \".\"]\n+ )\n+ self.assertEqual(\n+ result.output, \"Regex checks requested, but no regexes found.\\n\"\n+ )\n+\n+ @mock.patch(\"tartufo.cli.config.configure_regexes_from_args\")\n+ def test_command_fails_from_invalid_regex(self, mock_config_regex):\n+ mock_config_regex.side_effect = ValueError(\"Foo!\")\n+ runner = CliRunner()\n+ with runner.isolated_filesystem():\n+ result = runner.invoke(\n+ cli.main, [\"--repo-path\", \".\"]\n+ )\n+ self.assertEqual(result.output, \"Foo!\\n\")\n+\n+ @mock.patch(\"tartufo.cli.scanner.find_staged\")\n+ def test_command_calls_find_staged_for_pre_commit(\n+ self, mock_find_staged\n+ ):\n+ mock_find_staged.return_value = {}\n+ runner = CliRunner()\n+ with runner.isolated_filesystem():\n+ runner.invoke(\n+ cli.main, [\"--pre-commit\", \"--repo-path\", \"/\", \"--no-regex\", \"--entropy\"]\n+ )\n+ mock_find_staged.assert_called_once_with(\n+ \"/\",\n+ False,\n+ False,\n+ True,\n+ custom_regexes={},\n+ suppress_output=False,\n+ path_inclusions=[],\n+ path_exclusions=[]\n+ )\n+\n+ @mock.patch(\"tartufo.cli.scanner.find_strings\")\n+ def test_command_calls_find_strings_by_default(\n+ self, mock_find_strings\n+ ):\n+ mock_find_strings.return_value = {}\n+ runner = CliRunner()\n+ with runner.isolated_filesystem():\n+ runner.invoke(\n+ cli.main, [\"--no-regex\", \"--max-depth\", \"42\", \"--entropy\", \"git@github.com:godaddy/tartufo.git\"]\n )\n- )\n-\n- def test_parse_args_git_rules_multiple_times(self):\n- argv = [\"--git-rules\", \"file1\", \"--git-rules\", \"file2\"]\n- expected_rules_filenames = [\"file1\", \"file2\"]\n- args = cli.parse_args(argv)\n- self.assertEqual(\n- expected_rules_filenames, args.git_rules_filenames,\n- \"args.git_rules_filenames should be {}, is actually {}\".format(\n- expected_rules_filenames, args.rules_filenames\n+ mock_find_strings.assert_called_once_with(\n+ \"git@github.com:godaddy/tartufo.git\",\n+ None,\n+ 42,\n+ False,\n+ False,\n+ True,\n+ custom_regexes={},\n+ suppress_output=False,\n+ branch=None,\n+ repo_path=None,\n+ path_inclusions=[],\n+ path_exclusions=[]\n )\n- )\n-\n- def test_parse_args_rules_not_specified(self):\n- argv = []\n- args = cli.parse_args(argv)\n- self.assertEqual(0, len(args.rules_filenames), \"args.rules_filenames should be empty\")\n-\n- def test_parse_args_rules(self):\n- argv = [\"--rules\", \"file1\", \"file2\"]\n- expected_rules_filenames = [\"file1\", \"file2\"]\n- args = cli.parse_args(argv)\n- self.assertEqual(\n- expected_rules_filenames, args.rules_filenames,\n- \"args.rules_filenames should be {}, is actually {}\".format(\n- expected_rules_filenames, args.rules_filenames\n+\n+ @mock.patch(\"tartufo.cli.scanner.find_strings\")\n+ @mock.patch(\"tartufo.cli.util.clean_outputs\")\n+ def test_command_calls_cleanup_when_requested(\n+ self, mock_clean, mock_find_strings\n+ ):\n+ mock_find_strings.return_value = {\"foo\": \"bar\"}\n+ runner = CliRunner()\n+ with runner.isolated_filesystem():\n+ runner.invoke(\n+ cli.main,\n+ [\"--cleanup\", \"--no-regex\", \"--max-depth\", \"42\", \"--entropy\", \"git@github.com:godaddy/tartufo.git\"]\n+ )\n+ mock_clean.assert_called_once_with({\"foo\": \"bar\"})\n+\n+ @mock.patch(\"tartufo.cli.scanner.find_strings\")\n+ def test_path_inclusions(self, mock_find_strings):\n+ mock_find_strings.return_value = {}\n+ runner = CliRunner()\n+ with runner.isolated_filesystem():\n+ include_files = pathlib.Path(__file__).parent / \"data\" / \"include-files\"\n+ runner.invoke(\n+ cli.main,\n+ [\n+ \"-i\",\n+ str(include_files.resolve()),\n+ \"--no-regex\",\n+ \"--max-depth\",\n+ \"42\",\n+ \"--entropy\",\n+ \"git@github.com:godaddy/tartufo.git\"\n+ ]\n+ )\n+ mock_find_strings.assert_called_once_with(\n+ \"git@github.com:godaddy/tartufo.git\",\n+ None,\n+ 42,\n+ False,\n+ False,\n+ True,\n+ custom_regexes={},\n+ suppress_output=False,\n+ branch=None,\n+ repo_path=None,\n+ path_inclusions=[re.compile(\"tartufo/\"), re.compile(\"scripts/\")],\n+ path_exclusions=[]\n+ )\n+\n+ @mock.patch(\"tartufo.cli.scanner.find_strings\")\n+ def test_path_exclusions(self, mock_find_strings):\n+ mock_find_strings.return_value = {}\n+ runner = CliRunner()\n+ with runner.isolated_filesystem():\n+ exclude_files = pathlib.Path(__file__).parent / \"data\" / \"exclude-files\"\n+ runner.invoke(\n+ cli.main,\n+ [\n+ \"-x\",\n+ str(exclude_files.resolve()),\n+ \"--no-regex\",\n+ \"--max-depth\",\n+ \"42\",\n+ \"--entropy\",\n+ \"git@github.com:godaddy/tartufo.git\"\n+ ]\n+ )\n+ mock_find_strings.assert_called_once_with(\n+ \"git@github.com:godaddy/tartufo.git\",\n+ None,\n+ 42,\n+ False,\n+ False,\n+ True,\n+ custom_regexes={},\n+ suppress_output=False,\n+ branch=None,\n+ repo_path=None,\n+ path_inclusions=[],\n+ path_exclusions=[re.compile(\"tests/\"), re.compile(r\"\\.venv/\"), re.compile(r\".*\\.egg-info/\")]\n+ )\n+\n+ @mock.patch(\"tartufo.cli.scanner.find_strings\")\n+ def test_issues_path_is_called_out(self, mock_find_strings):\n+ mock_find_strings.return_value = {\"issues_path\": \"/foo\"}\n+ runner = CliRunner()\n+ with runner.isolated_filesystem():\n+ result = runner.invoke(\n+ cli.main,\n+ [\"git@github.com:godaddy/tartufo.git\"]\n )\n- )\n-\n- def test_parse_args_rules_multiple_times(self):\n- argv = [\"--rules\", \"file1\", \"--rules\", \"file2\"]\n- expected_rules_filenames = [\"file1\", \"file2\"]\n- args = cli.parse_args(argv)\n- self.assertEqual(\n- expected_rules_filenames, args.rules_filenames,\n- \"args.rules_filenames should be {}, is actually {}\".format(\n- expected_rules_filenames, args.rules_filenames\n+ self.assertEqual(result.output, \"Results have been saved in /foo\\n\")\n+\n+ @mock.patch(\"tartufo.cli.scanner.find_strings\")\n+ def test_command_exits_with_positive_return_code_when_issues_found(self, mock_find_strings):\n+ mock_find_strings.return_value = {\"found_issues\": True}\n+ runner = CliRunner()\n+ with runner.isolated_filesystem():\n+ result = runner.invoke(\n+ cli.main,\n+ [\"git@github.com:godaddy/tartufo.git\"]\n )\n- )\n-\n- def test_parse_args_rules_default_regexes_set_to_false(self):\n- argv = [\"--default-regexes\", \"f\"]\n- args = cli.parse_args(argv)\n- self.assertFalse(\n- args.do_default_regexes,\n- \"args.do_default_regexes should be False, is actually {}\".format(args.do_default_regexes)\n- )\n-\n- def test_parse_args_rules_default_regexes_set_to_true(self):\n- argv = [\"--default-regexes\", \"t\"]\n- args = cli.parse_args(argv)\n- self.assertTrue(\n- args.do_default_regexes,\n- \"args.do_default_regexes should be True, is actually {}\".format(args.do_default_regexes)\n- )\n-\n- def test_parse_args_rules_default_regexes_specified_with_no_value(self):\n- argv = [\"--default-regexes\", \"--regex\"]\n- args = cli.parse_args(argv)\n- self.assertTrue(\n- args.do_default_regexes,\n- \"args.do_default_regexes should be True, is actually {}\".format(args.do_default_regexes)\n- )\n-\n- def test_parse_args_rules_default_regexes_unset(self):\n- argv = []\n- args = cli.parse_args(argv)\n- self.assertTrue(\n- args.do_default_regexes,\n- \"args.do_default_regexes should be True, is actually {}\".format(args.do_default_regexes)\n- )\n+ self.assertGreater(result.exit_code, 0)\n \n \n if __name__ == \"__main__\":\ndiff --git a/tests/test_config_regexes.py b/tests/test_config.py\nsimilarity index 61%\nrename from tests/test_config_regexes.py\nrename to tests/test_config.py\n--- a/tests/test_config_regexes.py\n+++ b/tests/test_config.py\n@@ -1,24 +1,26 @@\n from __future__ import unicode_literals\n \n-import os.path\n import re\n import unittest\n-from collections import namedtuple\n \n from truffleHogRegexes.regexChecks import regexes as default_regexes\n \n from tartufo import config\n \n+try:\n+ import pathlib\n+except ImportError:\n+ import pathlib2 as pathlib # type: ignore\n+\n \n class ConfigureRegexTests(unittest.TestCase):\n \n def test_configure_regexes_from_args_rules_files_without_defaults(self):\n- rules_filenames = [os.path.join(os.path.dirname(__file__), \"data\", \"testRules.json\")]\n+ rules_path = pathlib.Path(__file__).parent / \"data\" / \"testRules.json\"\n+ rules_files = (rules_path.open(), )\n expected_regexes = {\"RSA private key 2\": re.compile(\"-----BEGIN EC PRIVATE KEY-----\")}\n \n- Args = namedtuple(\"Args\", (\"do_regex\", \"git_rules_repo\", \"git_rules_filenames\", \"rules_filenames\",\n- \"do_default_regexes\"))\n- args = Args(True, None, None, rules_filenames, False)\n+ args = {\"regex\": True, \"default_regexes\": False, \"rules\": rules_files}\n actual_regexes = config.configure_regexes_from_args(args, default_regexes)\n \n self.assertEqual(\n@@ -28,13 +30,12 @@ def test_configure_regexes_from_args_rules_files_without_defaults(self):\n )\n \n def test_configure_regexes_from_args_rules_files_with_defaults(self):\n- rules_filenames = [os.path.join(os.path.dirname(__file__), \"data\", \"testRules.json\")]\n+ rules_path = pathlib.Path(__file__).parent / \"data\" / \"testRules.json\"\n+ rules_files = (rules_path.open(), )\n expected_regexes = dict(default_regexes)\n expected_regexes[\"RSA private key 2\"] = re.compile(\"-----BEGIN EC PRIVATE KEY-----\")\n \n- Args = namedtuple(\"Args\", (\"do_regex\", \"git_rules_repo\", \"git_rules_filenames\", \"rules_filenames\",\n- \"do_default_regexes\"))\n- args = Args(True, None, None, rules_filenames, True)\n+ args = {\"regex\": True, \"default_regexes\": True, \"rules\": rules_files}\n actual_regexes = config.configure_regexes_from_args(args, default_regexes)\n \n self.assertEqual(\n@@ -44,22 +45,17 @@ def test_configure_regexes_from_args_rules_files_with_defaults(self):\n )\n \n def test_configure_regexes_from_args_no_do_regex(self):\n- rules_filenames = [\"testRules.json\"]\n-\n- Args = namedtuple(\"Args\", (\"do_regex\", \"git_rules_repo\", \"git_rules_filenames\", \"rules_filenames\",\n- \"do_default_regexes\"))\n- args = Args(False, None, None, rules_filenames, True)\n+ rules_path = pathlib.Path(__file__).parent / \"data\" / \"testRules.json\"\n+ rules_files = (rules_path.open(), )\n+ args = {\"regex\": False, \"default_regexes\": True, \"rules\": rules_files}\n actual_regexes = config.configure_regexes_from_args(args, default_regexes)\n \n self.assertEqual({}, actual_regexes, \"The regexes dictionary should be empty when do_regex is False\")\n \n def test_configure_regexes_from_args_no_rules(self):\n- rules_filenames = []\n expected_regexes = dict(default_regexes)\n \n- Args = namedtuple(\"Args\", (\"do_regex\", \"git_rules_repo\", \"git_rules_filenames\", \"rules_filenames\",\n- \"do_default_regexes\"))\n- args = Args(True, None, None, rules_filenames, False)\n+ args = {\"regex\": True, \"default_regexes\": True, \"rules\": ()}\n actual_regexes = config.configure_regexes_from_args(args, default_regexes)\n \n self.assertEqual(\n", "problem_statement": "", "hints_text": "", "created_at": "2019-11-15T18:22:32Z"}2 