diff --git a/secureli/abstractions/pre_commit.py b/secureli/abstractions/pre_commit.py index 658aa06a..7f4f6f24 100644 --- a/secureli/abstractions/pre_commit.py +++ b/secureli/abstractions/pre_commit.py @@ -54,27 +54,27 @@ def __init__( ): self.command_timeout_seconds = command_timeout_seconds - def install(self): + def install(self, folder_path: Path): """ Creates the pre-commit hook file in the .git directory so that `secureli scan` is run on each commit """ # Write pre-commit with invocation of `secureli scan` - pre_commit_hook = ".git/hooks/pre-commit" + pre_commit_hook = folder_path / ".git/hooks/pre-commit" with open(pre_commit_hook, "w") as f: f.write("#!/bin/sh\n") f.write("secureli scan\n") # Make pre-commit executable - f = Path(pre_commit_hook) - f.chmod(f.stat().st_mode | stat.S_IEXEC) + pre_commit_hook.chmod(pre_commit_hook.stat().st_mode | stat.S_IEXEC) def execute_hooks( - self, all_files: bool = False, hook_id: Optional[str] = None + self, folder_path: Path, all_files: bool = False, hook_id: Optional[str] = None ) -> ExecuteResult: """ Execute the configured hooks against the repository, either against your staged changes or all the files in the repo + :param folder_path: Indicates the git folder against which you run secureli :param all_files: True if we want to scan all files, default to false, which only scans our staged changes we're about to commit :param hook_id: A specific hook to run. If None, all hooks will be run @@ -94,7 +94,9 @@ def execute_hooks( if hook_id: subprocess_args.append(hook_id) - completed_process = subprocess.run(subprocess_args, stdout=subprocess.PIPE) + completed_process = subprocess.run( + subprocess_args, stdout=subprocess.PIPE, cwd=folder_path + ) output = ( completed_process.stdout.decode("utf8") if completed_process.stdout else "" ) @@ -105,6 +107,7 @@ def execute_hooks( def autoupdate_hooks( self, + folder_path: Path, bleeding_edge: bool = False, freeze: bool = False, repos: Optional[list] = None, @@ -112,6 +115,7 @@ def autoupdate_hooks( """ Updates the precommit hooks but executing precommit's autoupdate command. Additional info at https://pre-commit.com/#pre-commit-autoupdate + :param folder_path: Indicates the git folder against which you run secureli :param bleeding_edge: True if updating to the bleeding edge of the default branch instead of the latest tagged version (which is the default behavior) :param freeze: Set to True to store "frozen" hashes in rev instead of tag names. @@ -145,7 +149,9 @@ def autoupdate_hooks( subprocess_args.extend(repo_args) - completed_process = subprocess.run(subprocess_args, stdout=subprocess.PIPE) + completed_process = subprocess.run( + subprocess_args, stdout=subprocess.PIPE, cwd=folder_path + ) output = ( completed_process.stdout.decode("utf8") if completed_process.stdout else "" ) @@ -154,14 +160,17 @@ def autoupdate_hooks( else: return ExecuteResult(successful=True, output=output) - def update(self) -> ExecuteResult: + def update(self, folder_path: Path) -> ExecuteResult: """ Installs the hooks defined in pre-commit-config.yml. + :param folder_path: Indicates the git folder against which you run secureli :return: ExecuteResult, indicating success or failure. """ subprocess_args = ["pre-commit", "install-hooks", "--color", "always"] - completed_process = subprocess.run(subprocess_args, stdout=subprocess.PIPE) + completed_process = subprocess.run( + subprocess_args, stdout=subprocess.PIPE, cwd=folder_path + ) output = ( completed_process.stdout.decode("utf8") if completed_process.stdout else "" ) @@ -170,16 +179,19 @@ def update(self) -> ExecuteResult: else: return ExecuteResult(successful=True, output=output) - def remove_unused_hooks(self) -> ExecuteResult: + def remove_unused_hooks(self, folder_path: Path) -> ExecuteResult: """ Removes unused hook repos from the cache. Pre-commit determines which flags are "unused" by comparing the repos to the pre-commit-config.yaml file. Any cached hook repos that are not in the config file will be removed from the cache. + :param folder_path: Indicates the git folder against which you run secureli :return: ExecuteResult, indicating success or failure. """ subprocess_args = ["pre-commit", "gc", "--color", "always"] - completed_process = subprocess.run(subprocess_args, stdout=subprocess.PIPE) + completed_process = subprocess.run( + subprocess_args, stdout=subprocess.PIPE, cwd=folder_path + ) output = ( completed_process.stdout.decode("utf8") if completed_process.stdout else "" ) diff --git a/secureli/actions/action.py b/secureli/actions/action.py index 2e4428dc..fd1ddd84 100644 --- a/secureli/actions/action.py +++ b/secureli/actions/action.py @@ -165,7 +165,7 @@ def _install_secureli(self, folder_path: Path, always_yes: bool) -> VerifyResult self.action_deps.secureli_config.save(config) # Create seCureLI pre-commit hook with invocation of `secureli scan` - self.action_deps.updater.pre_commit.install() + self.action_deps.updater.pre_commit.install(folder_path) if secret_test_id := metadata.security_hook_id: self.action_deps.echo.print( @@ -173,7 +173,7 @@ def _install_secureli(self, folder_path: Path, always_yes: bool) -> VerifyResult ) scan_result = self.action_deps.scanner.scan_repo( - ScanMode.ALL_FILES, specific_test=secret_test_id + folder_path, ScanMode.ALL_FILES, specific_test=secret_test_id ) self.action_deps.echo.print(f"{scan_result.output}") diff --git a/secureli/actions/initializer.py b/secureli/actions/initializer.py index cf1d4b14..371c253f 100644 --- a/secureli/actions/initializer.py +++ b/secureli/actions/initializer.py @@ -25,7 +25,7 @@ def initialize_repo(self, folder_path: Path, reset: bool, always_yes: bool): """ # Will create a blank .secureli.yaml file if it does not exist - settings = self.action_deps.settings.load() + settings = self.action_deps.settings.load(folder_path) # Why are we saving settings? CLI should not be modifying them....just reading # With a templated example .secureli.yaml file, we won't be able to save diff --git a/secureli/actions/scan.py b/secureli/actions/scan.py index 0405b203..9eef390c 100644 --- a/secureli/actions/scan.py +++ b/secureli/actions/scan.py @@ -55,7 +55,7 @@ def scan_repo( if verify_result.outcome in self.halting_outcomes: return - scan_result = self.scanner.scan_repo(scan_mode, specific_test) + scan_result = self.scanner.scan_repo(folder_path, scan_mode, specific_test) details = scan_result.output or "Unknown output during scan" self.echo.print(details) diff --git a/secureli/actions/update.py b/secureli/actions/update.py index c2a5bb25..ea2d99ca 100644 --- a/secureli/actions/update.py +++ b/secureli/actions/update.py @@ -1,5 +1,5 @@ from typing import Optional - +from pathlib import Path from secureli.abstractions.echo import EchoAbstraction from secureli.services.logging import LoggingService, LogAction from secureli.services.updater import UpdaterService @@ -19,16 +19,17 @@ def __init__( self.logging = logging self.updater = updater - def update_hooks(self, latest: Optional[bool] = False): + def update_hooks(self, folder_path: Path, latest: Optional[bool] = False): """ Installs the hooks defined in pre-commit-config.yml. :param latest: Indicates whether you want to update to the latest versions of the installed hooks. + :param folder_path: Indicates the git folder against which you run secureli :return: ExecuteResult, indicating success or failure. """ if latest: self.echo.print("Updating hooks to the latest version...") - update_result = self.updater.update_hooks() + update_result = self.updater.update_hooks(folder_path) details = ( update_result.output or "Unknown output while updating hooks to latest version" @@ -42,7 +43,7 @@ def update_hooks(self, latest: Optional[bool] = False): self.logging.success(LogAction.update) else: self.echo.print("Beginning update...") - install_result = self.updater.update() + install_result = self.updater.update(folder_path) details = install_result.output or "Unknown output during hook installation" self.echo.print(details) if not install_result.successful: diff --git a/secureli/main.py b/secureli/main.py index 29d9c385..b0d0344c 100644 --- a/secureli/main.py +++ b/secureli/main.py @@ -1,6 +1,6 @@ from pathlib import Path from typing import Optional - +from typing_extensions import Annotated import typer from typer import Option @@ -10,6 +10,7 @@ from secureli.abstractions.echo import Color from secureli.resources import read_resource from secureli.settings import Settings +import secureli.repositories.secureli_config as SecureliConfig # Create SetupAction outside of DI, as it's not yet available. setup_action = SetupAction(epilog_template_data=read_resource("epilog.md")) @@ -48,11 +49,21 @@ def init( "-y", help="Say 'yes' to every prompt automatically without input", ), + directory: Annotated[ + Optional[Path], + Option( + ".", + "--directory", + "-d", + help="Run secureli against a specific directory", + ), + ] = ".", ): """ Detect languages and initialize pre-commit hooks and linters for the project """ - container.initializer_action().initialize_repo(Path("."), reset, yes) + SecureliConfig.FOLDER_PATH = Path(directory) + container.initializer_action().initialize_repo(Path(directory), reset, yes) @app.command() @@ -75,11 +86,21 @@ def scan( "-t", help="Limit the scan to a specific hook ID from your pre-commit config", ), + directory: Annotated[ + Optional[Path], + Option( + ".", + "--directory", + "-d", + help="Run secureli against a specific directory", + ), + ] = ".", ): """ Performs an explicit check of the repository to detect security issues without remote logging. """ - container.scan_action().scan_repo(Path("."), mode, yes, specific_test) + SecureliConfig.FOLDER_PATH = Path(directory) + container.scan_action().scan_repo(Path(directory), mode, yes, specific_test) @app.command(hidden=True) @@ -97,12 +118,22 @@ def update( "--latest", "-l", help="Update the installed pre-commit hooks to their latest versions", - ) + ), + directory: Annotated[ + Optional[Path], + Option( + ".", + "--directory", + "-d", + help="Run secureli against a specific directory", + ), + ] = ".", ): """ Update linters, configuration, and all else needed to maintain a secure repository. """ - container.update_action().update_hooks(latest) + SecureliConfig.FOLDER_PATH = Path(directory) + container.update_action().update_hooks(Path(directory), latest) if __name__ == "__main__": diff --git a/secureli/repositories/repo_files.py b/secureli/repositories/repo_files.py index 9c46dee0..83d7f4de 100644 --- a/secureli/repositories/repo_files.py +++ b/secureli/repositories/repo_files.py @@ -27,6 +27,7 @@ def list_repo_files(self, folder_path: Path) -> list[Path]: :return: The visible files within the specified repo as a list of Path objects """ git_path = folder_path / ".git" + if not git_path.exists() or not git_path.is_dir(): raise ValueError("The current folder is not a Git repository!") diff --git a/secureli/repositories/secureli_config.py b/secureli/repositories/secureli_config.py index 5e83485b..cc29cef1 100644 --- a/secureli/repositories/secureli_config.py +++ b/secureli/repositories/secureli_config.py @@ -5,6 +5,8 @@ from pydantic import BaseModel +FOLDER_PATH = Path(".") + class SecureliConfig(BaseModel): languages: Optional[list[str]] @@ -46,6 +48,7 @@ def load(self) -> SecureliConfig: """ secureli_folder_path = self._initialize_secureli_directory() secureli_config_path = secureli_folder_path / "repo-config.yaml" + if not secureli_config_path.exists(): return SecureliConfig() @@ -101,6 +104,7 @@ def _initialize_secureli_directory(self): Creates the .secureli folder within the current directory if needed. :return: The folder path of the .secureli folder that either exists or was just created. """ - secureli_folder_path = Path(".") / ".secureli" + + secureli_folder_path = Path(FOLDER_PATH) / ".secureli" secureli_folder_path.mkdir(parents=True, exist_ok=True) return secureli_folder_path diff --git a/secureli/repositories/settings.py b/secureli/repositories/settings.py index 905d915a..0cfcfda5 100644 --- a/secureli/repositories/settings.py +++ b/secureli/repositories/settings.py @@ -156,11 +156,12 @@ def save(self, settings: SecureliFile): with open(self.secureli_file_path, "w") as f: yaml.dump(settings_dict, f) - def load(self) -> SecureliFile: + def load(self, folder_path: Path) -> SecureliFile: """ Reads the contents of the .secureli.yaml file and returns it :return: SecureliFile containing the contents of the settings file """ + self.secureli_file_path = folder_path / ".secureli.yaml" if not self.secureli_file_path.exists(): return SecureliFile() diff --git a/secureli/services/git_ignore.py b/secureli/services/git_ignore.py index ef7e22a6..287ad74a 100644 --- a/secureli/services/git_ignore.py +++ b/secureli/services/git_ignore.py @@ -1,6 +1,7 @@ from pathlib import Path import pathspec +import secureli.repositories.secureli_config as SecureliConfig class BadIgnoreBlockError(Exception): @@ -17,18 +18,19 @@ class GitIgnoreService: header = "# Secureli-generated files (do not modify):" ignore_entries = [".secureli"] footer = "# End Secureli-generated files" - git_ignore_path = Path("./.gitignore") def ignore_secureli_files(self): """Creates a .gitignore, appends to an existing one, or updates the configuration""" - if not self.git_ignore_path.exists(): + git_ignore_path = SecureliConfig.FOLDER_PATH / "./.gitignore" + if not git_ignore_path.exists(): # your repo doesn't have a gitignore? That's a bold move. self._create_git_ignore() else: self._update_git_ignore() def ignored_file_patterns(self) -> list[str]: - if not self.git_ignore_path.exists(): + git_ignore_path = Path(SecureliConfig.FOLDER_PATH / "./.gitignore") + if not git_ignore_path.exists(): return [] """Reads the lines from the .gitignore file""" @@ -87,10 +89,12 @@ def _update_git_ignore(self): def _write_file_contents(self, contents: str): """Update the .gitignore file with the provided contents""" - with open(self.git_ignore_path, "w") as f: + git_ignore_path = SecureliConfig.FOLDER_PATH / "./.gitignore" + with open(git_ignore_path, "w") as f: f.write(contents) def _read_file_contents(self) -> str: """Read the .gitignore file""" - with open(self.git_ignore_path, "r") as f: + git_ignore_path = SecureliConfig.FOLDER_PATH / "./.gitignore" + with open(git_ignore_path, "r") as f: return f.read() diff --git a/secureli/services/language_config.py b/secureli/services/language_config.py index 1b47c843..307a8ffc 100644 --- a/secureli/services/language_config.py +++ b/secureli/services/language_config.py @@ -7,6 +7,7 @@ from secureli.resources.slugify import slugify from secureli.utilities.hash import hash_config from secureli.utilities.patterns import combine_patterns +import secureli.repositories.secureli_config as SecureliConfig class LanguageNotSupportedError(Exception): diff --git a/secureli/services/language_support.py b/secureli/services/language_support.py index 6fd034e8..3e492a7f 100644 --- a/secureli/services/language_support.py +++ b/secureli/services/language_support.py @@ -4,6 +4,7 @@ import pydantic import yaml +import secureli.repositories.secureli_config as SecureliConfig from secureli.abstractions.pre_commit import PreCommitAbstraction from secureli.resources.slugify import slugify from secureli.services.git_ignore import GitIgnoreService @@ -109,8 +110,7 @@ def apply_support(self, languages: list[str]) -> LanguageMetadata: as well as a secret-detection hook ID, if present. """ - path_to_pre_commit_file = Path(".pre-commit-config.yaml") - + path_to_pre_commit_file = SecureliConfig.FOLDER_PATH / ".pre-commit-config.yaml" # Raises a LanguageNotSupportedError if language doesn't resolve to a yaml file language_config_result = self._build_pre_commit_config(languages) @@ -250,11 +250,11 @@ def _write_pre_commit_configs( config_name = list(config.keys())[0] # generate relative file name and path. config_file_name = f"{slugify(language_linter_configs.language)}.{config_name}.yaml" - path_to_config_file = Path(f".secureli/{config_file_name}") - + path_to_config_file = ( + SecureliConfig.FOLDER_PATH / ".secureli/{config_file_name}" + ) with open(path_to_config_file, "w") as f: f.write(yaml.dump(config[config_name])) - num_configs_success += 1 except Exception as e: num_configs_non_success += 1 diff --git a/secureli/services/logging.py b/secureli/services/logging.py index ef66b7c6..eb9dfb1a 100644 --- a/secureli/services/logging.py +++ b/secureli/services/logging.py @@ -7,6 +7,7 @@ import pydantic +import secureli.repositories.secureli_config as SecureliConfig from secureli.services.language_support import LanguageSupportService, HookConfiguration from secureli.repositories.secureli_config import SecureliConfigRepository from secureli.utilities.git_meta import current_branch_name, git_user_email, origin_url @@ -130,7 +131,7 @@ def failure( def _log(self, log_entry: LogEntry): """Commit a log entry to the branch log file""" - log_folder_path = Path(f".secureli/logs") + log_folder_path = Path(SecureliConfig.FOLDER_PATH / ".secureli/logs") path_to_log = log_folder_path / f"{current_branch_name()}" # Do not simply mkdir the log folder path, in case the branch name contains diff --git a/secureli/services/scanner.py b/secureli/services/scanner.py index 60530329..9f65d5e2 100644 --- a/secureli/services/scanner.py +++ b/secureli/services/scanner.py @@ -63,7 +63,10 @@ def __init__(self, pre_commit: PreCommitAbstraction): self.pre_commit = pre_commit def scan_repo( - self, scan_mode: ScanMode, specific_test: Optional[str] = None + self, + folder_path: Path, + scan_mode: ScanMode, + specific_test: Optional[str] = None, ) -> ScanResult: """ Scans the repo according to the repo's seCureLI config @@ -74,8 +77,12 @@ def scan_repo( :return: A ScanResult object containing whether we succeeded and any error """ all_files = True if scan_mode == ScanMode.ALL_FILES else False - execute_result = self.pre_commit.execute_hooks(all_files, hook_id=specific_test) - parsed_output = self._parse_scan_ouput(output=execute_result.output) + execute_result = self.pre_commit.execute_hooks( + folder_path, all_files, hook_id=specific_test + ) + parsed_output = self._parse_scan_ouput( + folder_path, output=execute_result.output + ) return ScanResult( successful=execute_result.successful, @@ -83,7 +90,7 @@ def scan_repo( failures=parsed_output.failures, ) - def _parse_scan_ouput(self, output: str = "") -> ScanOuput: + def _parse_scan_ouput(self, folder_path: Path, output: str = "") -> ScanOuput: """ Parses the output from a scan and returns a list of Failure objects representing any hook rule failures during a scan. @@ -92,7 +99,7 @@ def _parse_scan_ouput(self, output: str = "") -> ScanOuput: """ failures = [] failure_indexes = [] - config_data = self._get_config() + config_data = self._get_config(folder_path) # Split the output up by each line and record the index of each failure output_by_line = output.split("\n") @@ -186,12 +193,12 @@ def _find_repo_from_id(self, hook_id: str, config: dict): return OutputParseErrors.REPO_NOT_FOUND - def _get_config(self): + def _get_config(self, folder_path: Path): """ Gets the contents of the .pre-commit-config file and returns it as a dict :return: Dict containing the contents of the .pre-commit-config.yaml file """ - path_to_config = Path(".pre-commit-config.yaml") + path_to_config = folder_path / ".pre-commit-config.yaml" with open(path_to_config, "r") as f: data = yaml.safe_load(f) return data diff --git a/secureli/services/updater.py b/secureli/services/updater.py index ee896d5f..e5be92fd 100644 --- a/secureli/services/updater.py +++ b/secureli/services/updater.py @@ -1,5 +1,5 @@ from typing import Optional - +from pathlib import Path import pydantic from secureli.abstractions.pre_commit import PreCommitAbstraction @@ -30,6 +30,7 @@ def __init__( def update_hooks( self, + folder_path: Path, bleeding_edge: bool = False, freeze: bool = False, repos: Optional[list] = None, @@ -37,13 +38,16 @@ def update_hooks( """ Updates the precommit hooks but executing precommit's autoupdate command. Additional info at https://pre-commit.com/#pre-commit-autoupdate + :param folder_path: Indicates the git folder against which you run secureli :param bleeding_edge: True if updating to the bleeding edge of the default branch instead of the latest tagged version (which is the default behavior) :param freeze: Set to True to store "frozen" hashes in rev instead of tag names. :param repos: Dectionary of repos to update. This is used to target specific repos instead of all repos. :return: ExecuteResult, indicating success or failure. """ - update_result = self.pre_commit.autoupdate_hooks(bleeding_edge, freeze, repos) + update_result = self.pre_commit.autoupdate_hooks( + folder_path, bleeding_edge, freeze, repos + ) output = update_result.output if update_result.successful and not output: @@ -55,22 +59,23 @@ def update_hooks( return UpdateResult(successful=update_result.successful, output=output) - def update(self): + def update(self, folder_path: Path): """ Updates secureli with the latest local configuration. + :param folder_path: Indicates the git folder against which you run secureli :return: ExecuteResult, indicating success or failure. """ update_message = "Updating .pre-commit-config.yaml...\n" output = update_message - hook_install_result = self.pre_commit.update() + hook_install_result = self.pre_commit.update(folder_path) output += hook_install_result.output if hook_install_result.successful and output == update_message: output += "No changes necessary.\n" if hook_install_result.successful and hook_install_result.output: - prune_result = self.pre_commit.remove_unused_hooks() + prune_result = self.pre_commit.remove_unused_hooks(folder_path) output += "\nRemoving unused environments:\n" + prune_result.output return UpdateResult(successful=hook_install_result.successful, output=output) diff --git a/secureli/settings.py b/secureli/settings.py index 8e6adfe6..3527dbcd 100644 --- a/secureli/settings.py +++ b/secureli/settings.py @@ -4,6 +4,7 @@ import pydantic import yaml +import secureli.repositories.secureli_config as SecureliConfig from secureli.repositories.settings import ( RepoFilesSettings, EchoSettings, @@ -19,7 +20,7 @@ def secureli_yaml_settings( """ encoding = settings.__config__.env_file_encoding - path_to_settings = Path(".secureli.yaml") + path_to_settings = Path(SecureliConfig.FOLDER_PATH / ".secureli.yaml") if not path_to_settings.exists(): return {} with open( diff --git a/tests/abstractions/test_pre_commit.py b/tests/abstractions/test_pre_commit.py index f1edac89..1a901be5 100644 --- a/tests/abstractions/test_pre_commit.py +++ b/tests/abstractions/test_pre_commit.py @@ -9,6 +9,31 @@ from secureli.abstractions.pre_commit import ( PreCommitAbstraction, ) +from secureli.repositories.settings import ( + PreCommitSettings, + PreCommitRepo, + PreCommitHook, +) + +test_folder_path = Path("does-not-matter") + + +@pytest.fixture() +def settings_dict() -> dict: + return PreCommitSettings( + repos=[ + PreCommitRepo( + url="http://example-repo.com/", + hooks=[ + PreCommitHook( + id="hook-id", + arguments=None, + additional_args=None, + ) + ], + ) + ] + ).dict() @pytest.fixture() @@ -62,7 +87,7 @@ def test_that_pre_commit_executes_hooks_successfully( mock_subprocess: MagicMock, ): mock_subprocess.run.return_value = CompletedProcess(args=[], returncode=0) - execute_result = pre_commit.execute_hooks() + execute_result = pre_commit.execute_hooks(test_folder_path) assert execute_result.successful assert "--all-files" not in mock_subprocess.run.call_args_list[0].args[0] @@ -73,7 +98,7 @@ def test_that_pre_commit_executes_hooks_successfully_including_all_files( mock_subprocess: MagicMock, ): mock_subprocess.run.return_value = CompletedProcess(args=[], returncode=0) - execute_result = pre_commit.execute_hooks(all_files=True) + execute_result = pre_commit.execute_hooks(test_folder_path, all_files=True) assert execute_result.successful assert "--all-files" in mock_subprocess.run.call_args_list[0].args[0] @@ -84,7 +109,7 @@ def test_that_pre_commit_executes_hooks_and_reports_failures( mock_subprocess: MagicMock, ): mock_subprocess.run.return_value = CompletedProcess(args=[], returncode=1) - execute_result = pre_commit.execute_hooks() + execute_result = pre_commit.execute_hooks(test_folder_path) assert not execute_result.successful @@ -94,7 +119,7 @@ def test_that_pre_commit_executes_a_single_hook_if_specified( mock_subprocess: MagicMock, ): mock_subprocess.run.return_value = CompletedProcess(args=[], returncode=0) - pre_commit.execute_hooks(hook_id="detect-secrets") + pre_commit.execute_hooks(test_folder_path, hook_id="detect-secrets") assert mock_subprocess.run.call_args_list[0].args[0][-1] == "detect-secrets" @@ -105,7 +130,7 @@ def test_that_pre_commit_autoupdate_hooks_executes_successfully( mock_subprocess: MagicMock, ): mock_subprocess.run.return_value = CompletedProcess(args=[], returncode=0) - execute_result = pre_commit.autoupdate_hooks() + execute_result = pre_commit.autoupdate_hooks(test_folder_path) assert execute_result.successful @@ -115,7 +140,7 @@ def test_that_pre_commit_autoupdate_hooks_properly_handles_failed_executions( mock_subprocess: MagicMock, ): mock_subprocess.run.return_value = CompletedProcess(args=[], returncode=1) - execute_result = pre_commit.autoupdate_hooks() + execute_result = pre_commit.autoupdate_hooks(test_folder_path) assert not execute_result.successful @@ -125,7 +150,7 @@ def test_that_pre_commit_autoupdate_hooks_executes_successfully_with_bleeding_ed mock_subprocess: MagicMock, ): mock_subprocess.run.return_value = CompletedProcess(args=[], returncode=0) - execute_result = pre_commit.autoupdate_hooks(bleeding_edge=True) + execute_result = pre_commit.autoupdate_hooks(test_folder_path, bleeding_edge=True) assert execute_result.successful assert "--bleeding-edge" in mock_subprocess.run.call_args_list[0].args[0] @@ -136,7 +161,7 @@ def test_that_pre_commit_autoupdate_hooks_executes_successfully_with_freeze( mock_subprocess: MagicMock, ): mock_subprocess.run.return_value = CompletedProcess(args=[], returncode=0) - execute_result = pre_commit.autoupdate_hooks(freeze=True) + execute_result = pre_commit.autoupdate_hooks(test_folder_path, freeze=True) assert execute_result.successful assert "--freeze" in mock_subprocess.run.call_args_list[0].args[0] @@ -148,7 +173,7 @@ def test_that_pre_commit_autoupdate_hooks_executes_successfully_with_repos( ): test_repos = ["some-repo-url"] mock_subprocess.run.return_value = CompletedProcess(args=[], returncode=0) - execute_result = pre_commit.autoupdate_hooks(repos=test_repos) + execute_result = pre_commit.autoupdate_hooks(test_folder_path, repos=test_repos) assert execute_result.successful assert "--repo some-repo-url" in mock_subprocess.run.call_args_list[0].args[0] @@ -160,7 +185,7 @@ def test_that_pre_commit_autoupdate_hooks_executes_successfully_with_multiple_re ): test_repos = ["some-repo-url", "some-other-repo-url"] mock_subprocess.run.return_value = CompletedProcess(args=[], returncode=0) - execute_result = pre_commit.autoupdate_hooks(repos=test_repos) + execute_result = pre_commit.autoupdate_hooks(test_folder_path, repos=test_repos) assert execute_result.successful assert "--repo some-repo-url" in mock_subprocess.run.call_args_list[0].args[0] @@ -173,7 +198,7 @@ def test_that_pre_commit_autoupdate_hooks_fails_with_repos_containing_non_string ): test_repos = [{"something": "something-else"}] mock_subprocess.run.return_value = CompletedProcess(args=[], returncode=0) - execute_result = pre_commit.autoupdate_hooks(repos=test_repos) + execute_result = pre_commit.autoupdate_hooks(test_folder_path, repos=test_repos) assert not execute_result.successful @@ -184,7 +209,7 @@ def test_that_pre_commit_autoupdate_hooks_ignores_repos_when_repos_is_a_dict( ): test_repos = {} mock_subprocess.run.return_value = CompletedProcess(args=[], returncode=0) - execute_result = pre_commit.autoupdate_hooks(repos=test_repos) + execute_result = pre_commit.autoupdate_hooks(test_folder_path, repos=test_repos) assert execute_result.successful assert "--repo {}" not in mock_subprocess.run.call_args_list[0].args[0] @@ -196,7 +221,7 @@ def test_that_pre_commit_autoupdate_hooks_converts_repos_when_repos_is_a_string( ): test_repos = "string" mock_subprocess.run.return_value = CompletedProcess(args=[], returncode=0) - execute_result = pre_commit.autoupdate_hooks(repos=test_repos) + execute_result = pre_commit.autoupdate_hooks(test_folder_path, repos=test_repos) assert execute_result.successful assert "--repo string" in mock_subprocess.run.call_args_list[0].args[0] @@ -208,7 +233,7 @@ def test_that_pre_commit_update_executes_successfully( mock_subprocess: MagicMock, ): mock_subprocess.run.return_value = CompletedProcess(args=[], returncode=0) - execute_result = pre_commit.update() + execute_result = pre_commit.update(test_folder_path) assert execute_result.successful @@ -218,7 +243,7 @@ def test_that_pre_commit_update_properly_handles_failed_executions( mock_subprocess: MagicMock, ): mock_subprocess.run.return_value = CompletedProcess(args=[], returncode=1) - execute_result = pre_commit.update() + execute_result = pre_commit.update(test_folder_path) assert not execute_result.successful @@ -229,7 +254,7 @@ def test_that_pre_commit_remove_unused_hookss_executes_successfully( mock_subprocess: MagicMock, ): mock_subprocess.run.return_value = CompletedProcess(args=[], returncode=0) - execute_result = pre_commit.remove_unused_hooks() + execute_result = pre_commit.remove_unused_hooks(test_folder_path) assert execute_result.successful @@ -239,7 +264,7 @@ def test_that_pre_commit_remove_unused_hooks_properly_handles_failed_executions( mock_subprocess: MagicMock, ): mock_subprocess.run.return_value = CompletedProcess(args=[], returncode=1) - execute_result = pre_commit.remove_unused_hooks() + execute_result = pre_commit.remove_unused_hooks(test_folder_path) assert not execute_result.successful @@ -255,7 +280,7 @@ def test_that_pre_commit_install_creates_pre_commit_hook_for_secureli( ): mock_exists.return_value = True - pre_commit.install() + pre_commit.install(test_folder_path) mock_open.assert_called_once() mock_chmod.assert_called_once() diff --git a/tests/actions/test_update_action.py b/tests/actions/test_update_action.py index 03e79f95..fbce1cf3 100644 --- a/tests/actions/test_update_action.py +++ b/tests/actions/test_update_action.py @@ -1,11 +1,13 @@ from unittest.mock import MagicMock - +from pathlib import Path import pytest from secureli.actions.action import ActionDependencies from secureli.actions.update import UpdateAction from secureli.services.updater import UpdateResult +test_folder_path = Path("does-not-matter") + @pytest.fixture() def mock_scanner() -> MagicMock: @@ -65,7 +67,7 @@ def test_that_update_action_executes_successfully( successful=True, output="Some update performed" ) - update_action.update_hooks() + update_action.update_hooks(test_folder_path) mock_echo.print.assert_called_with("Update executed successfully.") @@ -79,7 +81,7 @@ def test_that_update_action_handles_failed_execution( successful=False, output="Failed to update" ) - update_action.update_hooks() + update_action.update_hooks(test_folder_path) mock_echo.print.assert_called_with("Failed to update") @@ -88,7 +90,7 @@ def test_that_latest_flag_initiates_update( update_action: UpdateAction, mock_echo: MagicMock, ): - update_action.update_hooks(latest=True) + update_action.update_hooks(test_folder_path, latest=True) mock_echo.print.assert_called_with("Hooks successfully updated to latest version") @@ -101,6 +103,6 @@ def test_that_latest_flag_handles_failed_update( mock_updater.update_hooks.return_value = UpdateResult( successful=False, output="Update failed" ) - update_action.update_hooks(latest=True) + update_action.update_hooks(test_folder_path, latest=True) mock_echo.print.assert_called_with("Update failed") diff --git a/tests/repositories/test_secureli_config.py b/tests/repositories/test_secureli_config.py index 76b9a407..0f3a4d84 100644 --- a/tests/repositories/test_secureli_config.py +++ b/tests/repositories/test_secureli_config.py @@ -1,8 +1,9 @@ +from pathlib import Path from unittest.mock import MagicMock - import pytest from pytest_mock import MockerFixture +import secureli.repositories.secureli_config as SecureliConfigAll from secureli.repositories.secureli_config import ( SecureliConfigRepository, SecureliConfig, diff --git a/tests/repositories/test_settings_repository.py b/tests/repositories/test_settings_repository.py index c32f213c..8787a267 100644 --- a/tests/repositories/test_settings_repository.py +++ b/tests/repositories/test_settings_repository.py @@ -70,7 +70,7 @@ def test_that_settings_file_loads_settings_when_present( existent_path: MagicMock, settings_repository: SecureliRepository, ): - secureli_file = settings_repository.load() + secureli_file = settings_repository.load(existent_path) assert secureli_file.echo.level == EchoLevel.error @@ -79,7 +79,7 @@ def test_that_settings_file_created_when_not_present( non_existent_path: MagicMock, settings_repository: SecureliRepository, ): - secureli_file = settings_repository.load() + secureli_file = settings_repository.load(non_existent_path) assert secureli_file is not None diff --git a/tests/services/test_scanner_service.py b/tests/services/test_scanner_service.py index fa78b412..c9c4d562 100644 --- a/tests/services/test_scanner_service.py +++ b/tests/services/test_scanner_service.py @@ -1,11 +1,13 @@ from unittest.mock import MagicMock - +from pathlib import Path import pytest from secureli.abstractions.pre_commit import ExecuteResult from secureli.services.scanner import ScannerService, ScanMode, OutputParseErrors from pytest_mock import MockerFixture +test_folder_path = Path(".") + @pytest.fixture() def mock_scan_output_no_failure(): @@ -115,7 +117,7 @@ def test_that_scanner_service_scans_repositories_with_pre_commit( scanner_service: ScannerService, mock_pre_commit: MagicMock, ): - scan_result = scanner_service.scan_repo(ScanMode.ALL_FILES) + scan_result = scanner_service.scan_repo(test_folder_path, ScanMode.ALL_FILES) mock_pre_commit.execute_hooks.assert_called_once() assert scan_result.successful @@ -130,7 +132,7 @@ def test_that_scanner_service_parses_failures( mock_pre_commit.execute_hooks.return_value = ExecuteResult( successful=True, output=mock_scan_output_single_failure ) - scan_result = scanner_service.scan_repo(ScanMode.ALL_FILES) + scan_result = scanner_service.scan_repo(test_folder_path, ScanMode.ALL_FILES) assert len(scan_result.failures) is 1 @@ -144,7 +146,7 @@ def test_that_scanner_service_parses_multiple_failures( mock_pre_commit.execute_hooks.return_value = ExecuteResult( successful=True, output=mock_scan_output_double_failure ) - scan_result = scanner_service.scan_repo(ScanMode.ALL_FILES) + scan_result = scanner_service.scan_repo(test_folder_path, ScanMode.ALL_FILES) assert len(scan_result.failures) is 2 @@ -158,7 +160,7 @@ def test_that_scanner_service_parses_when_no_failures( mock_pre_commit.execute_hooks.return_value = ExecuteResult( successful=True, output=mock_scan_output_no_failure ) - scan_result = scanner_service.scan_repo(ScanMode.ALL_FILES) + scan_result = scanner_service.scan_repo(test_folder_path, ScanMode.ALL_FILES) assert len(scan_result.failures) is 0 @@ -172,6 +174,6 @@ def test_that_scanner_service_handles_error_in_missing_repo( mock_pre_commit.execute_hooks.return_value = ExecuteResult( successful=True, output=mock_scan_output_double_failure ) - scan_result = scanner_service.scan_repo(ScanMode.ALL_FILES) + scan_result = scanner_service.scan_repo(test_folder_path, ScanMode.ALL_FILES) assert scan_result.failures[1].repo == OutputParseErrors.REPO_NOT_FOUND diff --git a/tests/services/test_updater_service.py b/tests/services/test_updater_service.py index f1602c17..de822456 100644 --- a/tests/services/test_updater_service.py +++ b/tests/services/test_updater_service.py @@ -1,10 +1,12 @@ from unittest.mock import MagicMock - +from pathlib import Path import pytest from secureli.abstractions.pre_commit import ExecuteResult from secureli.services.updater import UpdaterService +test_folder_path = Path("does-not-matter") + @pytest.fixture() def updater_service( @@ -23,7 +25,7 @@ def test_that_updater_service_update_updates_and_prunes_with_pre_commit( mock_pre_commit.remove_unused_hooks.return_value = ExecuteResult( successful=True, output=output ) - update_result = updater_service.update() + update_result = updater_service.update(test_folder_path) mock_pre_commit.update.assert_called_once() mock_pre_commit.remove_unused_hooks.assert_called_once() @@ -39,7 +41,7 @@ def test_that_updater_service_update_does_not_prune_if_no_updates( mock_pre_commit.remove_unused_hooks.return_value = ExecuteResult( successful=True, output=output ) - update_result = updater_service.update() + update_result = updater_service.update(test_folder_path) mock_pre_commit.update.assert_called_once() mock_pre_commit.remove_unused_hooks.assert_not_called() @@ -58,7 +60,7 @@ def test_that_updater_service_update_hooks_updates_with_pre_commit( mock_pre_commit.remove_unused_hooks.return_value = ExecuteResult( successful=True, output=output ) - update_result = updater_service.update_hooks() + update_result = updater_service.update_hooks(test_folder_path) mock_pre_commit.autoupdate_hooks.assert_called_once() assert update_result.successful @@ -75,7 +77,7 @@ def test_that_updater_service_update_hooks_handles_no_updates_successfully( mock_pre_commit.remove_unused_hooks.return_value = ExecuteResult( successful=True, output=output ) - update_result = updater_service.update_hooks() + update_result = updater_service.update_hooks(test_folder_path) mock_pre_commit.autoupdate_hooks.assert_called_once() assert update_result.successful