def git_calls(self, mock_rootdir): """We need to do a bunch of mocking, because there's a lot of git operations. This function handles all that. """ with open('test_data/sample.diff') as f: diff_content = f.read() return [ # fetching latest changes SubprocessMock( expected_input='git rev-parse --abbrev-ref HEAD', mocked_output='master', ), SubprocessMock( expected_input='git fetch --quiet origin master:master --force', ), # get diff (filtering out ignored file extensions) SubprocessMock( expected_input= 'git diff sha256-hash HEAD --name-only --diff-filter ACM', mocked_output='examples/aws_credentials.json', ), SubprocessMock( expected_input= 'git diff sha256-hash HEAD -- examples/aws_credentials.json', mocked_output=diff_content, ), # get baseline file SubprocessMock( expected_input='git show HEAD:foobar', mocked_output=b'', ), ]
def get_subprocess_mocks(secrets, updates_repo): """ :type secrets: SecretsCollection :type updates_repo: bool """ subprocess_mocks = [] if secrets.data: # TODO: If we need to, we should modify this for more filenames secrets_dict = secrets.json() filenames = list(secrets_dict.keys()) subprocess_mocks.append( # First, we get the main branch SubprocessMock( expected_input='git rev-parse --abbrev-ref HEAD', mocked_output='master', ), ) subprocess_mocks.append( # then, we get the blame info for that branch SubprocessMock( expected_input=( 'git blame master -L {},{} --show-email ' '--line-porcelain -- {}'.format( secrets_dict[filenames[0]][0]['line_number'], secrets_dict[filenames[0]][0]['line_number'], filenames[0], ) ), mocked_output=mock_blame_info(), ), ) subprocess_mocks.append( # and get the current HEAD. SubprocessMock( expected_input='git rev-parse HEAD', mocked_output='new_sha', ), ) if updates_repo: # We also get the current HEAD when updating. subprocess_mocks.append( SubprocessMock( expected_input='git rev-parse HEAD', mocked_output='new_sha', ), ) return subprocess_mocks
def test_scan_nonexistent_last_saved_hash(self, mock_logic, mock_rootdir): calls = self.git_calls(mock_rootdir) calls[-2] = SubprocessMock( expected_input= 'git diff sha256-hash HEAD -- examples/aws_credentials.json', mocked_output=b'fatal: the hash is not in git history', should_throw_exception=True, ) calls[-1] = SubprocessMock(expected_input='git rev-parse HEAD', ) repo = mock_logic() with mock_git_calls(*calls): secrets = repo.scan() assert secrets.data == {}
def test_add_local_repo(self, mock_file_operations, mock_rootdir): # This just needs to exist; no actual operations will be done to this. repo = 'examples' git_calls = [ # repo.update SubprocessMock( expected_input='git rev-parse HEAD', mocked_output='mocked_sha', ), ] with mock_git_calls(*git_calls): args = self.parse_args( 'add {} --baseline .secrets.baseline --local --root-dir {}'. format( repo, mock_rootdir, )) add_repo(args) mock_file_operations.write.assert_called_with( metadata_factory( sha='mocked_sha', repo=os.path.abspath( os.path.join( os.path.dirname(__file__), '../../examples', ), ), baseline_filename='.secrets.baseline', json=True, ), )
def test_success(self, mock_logic): repo = mock_logic() with mock_git_calls( SubprocessMock( expected_input='git rev-parse HEAD', mocked_output='new_hash', )): repo.update() assert repo.last_commit_hash == 'new_hash'
def add_non_local_repo(self, mock_rootdir): repo = '[email protected]:yelp/detect-secrets' directory = '{}/repos/{}'.format( mock_rootdir, BaseStorage.hash_filename('yelp/detect-secrets'), ) git_calls = [ SubprocessMock(expected_input='git clone {} {} --bare'.format( repo, directory), ), SubprocessMock( expected_input='git rev-parse HEAD', mocked_output='mocked_sha', ), ] with mock_git_calls(*git_calls): args = self.parse_args('add {} --root-dir {}'.format( repo, mock_rootdir)) add_repo(args)
def test_name(self, repo, name, local_storage): """OK, I admit this is kinda a lame test case, because technically everything is mocked out. However, it's needed for coverage, and it *does* test things (kinda). """ with mock_git_calls( SubprocessMock( expected_input='git remote get-url origin', mocked_output='[email protected]:yelp/detect-secrets', ), ), assert_directories_created(): assert local_storage.setup(repo).repository_name == name
def test_error_when_getting_git_tracked_files(self, path): with mock_git_calls( 'detect_secrets.core.baseline.subprocess.check_output', (SubprocessMock( expected_input='git -C ./test_data/files ls-files', should_throw_exception=True, mocked_output='', ), ), ): results = self.get_results(path=['./test_data/files']) assert not results
def test_no_files_in_git_repo(self): with mock_git_calls( 'detect_secrets.core.baseline.subprocess.check_output', (SubprocessMock( expected_input='git ls-files will_be_mocked', should_throw_exception=True, mocked_output='', ), ), ): results = self.get_results(path=['will_be_mocked']) assert not results
def construct_subprocess_mock_git_clone(repo, mocked_output, mock_rootdir): return SubprocessMock( expected_input=( 'git clone [email protected]:yelp/detect-secrets {} --bare'. format( '{}/repos/{}'.format( mock_rootdir, repo.hash_filename('yelp/detect-secrets'), ), )), mocked_output=mocked_output, should_throw_exception=True, )
def test_repositories_added_can_be_scanned(self, mock_rootdir, repo_to_scan): directory = '{}/repos/{}'.format( mock_rootdir, BaseStorage.hash_filename('Yelp/detect-secrets'), ) mocked_sha = 'aabbcc' # We don't **actually** want to clone the repo per test run. with mock_git_calls( SubprocessMock(expected_input=( 'git clone https://github.com/Yelp/detect-secrets {} --bare' ).format(directory, ), ), # Since there is no prior sha to retrieve SubprocessMock( expected_input='git rev-parse HEAD', mocked_output=mocked_sha, )): assert main([ 'add', 'https://github.com/Yelp/detect-secrets', '--root-dir', mock_rootdir, ]) == 0 with mock_git_calls( # Getting latest changes SubprocessMock( expected_input='git rev-parse --abbrev-ref HEAD', mocked_output='master', ), SubprocessMock( expected_input= 'git fetch --quiet origin master:master --force', ), # Getting relevant diff SubprocessMock( expected_input= 'git diff {} HEAD --name-only --diff-filter ACM'.format( mocked_sha), mocked_output='filenameA', ), SubprocessMock( expected_input='git diff {} HEAD -- filenameA'.format( mocked_sha), mocked_output='', ), # Storing latest sha SubprocessMock(expected_input='git rev-parse HEAD', ), ): assert main([ 'scan', repo_to_scan, '--root-dir', mock_rootdir, ]) == 0
def test_unable_to_find_baseline(self, mock_logic, mock_rootdir): calls = self.git_calls(mock_rootdir) calls[-1] = SubprocessMock( expected_input='git show HEAD:foobar', mocked_output=b'fatal: Path \'foobar\' does not exist', should_throw_exception=True, ) repo = mock_logic() with mock_git_calls(*calls): secrets = repo.scan() assert len(secrets.data['examples/aws_credentials.json']) == 2
def test_quit_if_baseline_is_changed_but_not_staged(self, mock_log): with mock_git_calls( 'detect_secrets.pre_commit_hook.subprocess.check_output', (SubprocessMock( expected_input='git diff --name-only', mocked_output=b'baseline.file', ), ), ): assert_commit_blocked( '--baseline baseline.file test_data/files/file_with_secrets.py', ) assert mock_log.error_messages == ( 'Your baseline file (baseline.file) is unstaged.\n' '`git add baseline.file` to fix this.\n')
def test_scan_with_baseline(self, mock_logic, mock_rootdir): baseline = json.dumps({ 'results': { 'examples/aws_credentials.json': [ { 'type': 'Hex High Entropy String', 'hashed_secret': '2353d31737bbbdb10eb97466b8f2dc057ead1432', 'line_number': 3, # does not matter }, { 'type': 'AWS Access Key', 'hashed_secret': '25910f981e85ca04baf359199dd0bd4a3ae738b6', 'line_number': 3, # does not matter }, { 'type': 'IBM COS HMAC Credentials', 'hashed_secret': '9c6e0753631454e4ab8d896c242dcf4f8300fd57', 'line_number': 3, # does not matter }, ], }, 'exclude_regex': '', 'plugins_used': [ { 'hex_limit': 3.5, 'name': 'HexHighEntropyString', }, { 'name': 'AWSKeyDetector', }, { 'name': 'IbmCosHmacDetector', }, ], }) calls = self.git_calls(mock_rootdir) calls[-1] = SubprocessMock( expected_input='git show HEAD:foobar', mocked_output=baseline, ) repo = mock_logic() with mock_git_calls(*calls): secrets = repo.scan() assert len(secrets.data) == 0
def test_add_s3_backend_repo(self, mock_file_operations, mocked_boto): args = self.parse_args( 'add {} ' '--local ' '--storage s3 ' '--s3-credentials-file examples/aws_credentials.json ' '--s3-bucket pail'.format('examples'), has_s3=True, ) git_calls = [ # repo.update SubprocessMock( expected_input='git rev-parse HEAD', mocked_output='mocked_sha', ), ] with mock_git_calls(*git_calls): mocked_boto.list_objects_v2.return_value = {} add_repo(args)