import argparse import subprocess from unittest import mock from locutus.init import run_init def make_args(profile_path): args = argparse.Namespace() args.profile = profile_path return args @mock.patch('builtins.input') @mock.patch('getpass.getpass') @mock.patch('subprocess.run') def test_run_init_success_no_passphrase( mock_subproc, mock_getpass, mock_input, tmp_path ): # Simulate user enters repo path and empty passphrase mock_input.return_value = '/test/repo' mock_getpass.return_value = '' mock_subproc.return_value = mock.Mock(returncode=0) rc_path = tmp_path / 'test.rc' args = make_args(str(rc_path)) result = run_init(args) assert result == 0 with open(rc_path) as f: content = f.read() assert 'export BORG_REPO="/test/repo"' in content assert 'BORG_PASSPHRASE' not in content mock_subproc.assert_called_once() call_args = mock_subproc.call_args[0][0] assert call_args == ['borg', 'init', '--encryption=none', '/test/repo'] @mock.patch('builtins.input') @mock.patch('getpass.getpass') @mock.patch('subprocess.run') def test_run_init_success_with_passphrase( mock_subproc, mock_getpass, mock_input, tmp_path ): mock_input.return_value = '/secure/repo' # First call is passphrase, second is confirm, both match mock_getpass.side_effect = ['hunter2', 'hunter2'] mock_subproc.return_value = mock.Mock(returncode=0) rc_path = tmp_path / 'secure.rc' args = make_args(str(rc_path)) result = run_init(args) assert result == 0 with open(rc_path) as f: content = f.read() assert 'export BORG_REPO="/secure/repo"' in content assert 'export BORG_PASSPHRASE="hunter2"' in content mock_subproc.assert_called_once() call_args = mock_subproc.call_args[0][0] assert call_args == ['borg', 'init', '--encryption=repokey', '/secure/repo'] @mock.patch('builtins.input') def test_run_init_empty_repo_aborts(mock_input, tmp_path): mock_input.return_value = '' args = make_args(str(tmp_path / 'fail.rc')) result = run_init(args) assert result == 1 @mock.patch('builtins.input') @mock.patch('getpass.getpass') def test_run_init_passphrase_mismatch(mock_getpass, mock_input, tmp_path): mock_input.return_value = '/fail/repo' mock_getpass.side_effect = [ 'pass1', 'pass2', '', ] # Third call: user gives up args = make_args(str(tmp_path / 'fail2.rc')) with mock.patch('subprocess.run'): result = run_init(args) assert result == 0 # Exits with no passphrase (encryption=none) with open(args.profile) as f: content = f.read() assert 'BORG_PASSPHRASE' not in content @mock.patch('builtins.input') @mock.patch('getpass.getpass') @mock.patch('subprocess.run', side_effect=Exception('init failed')) def test_run_init_subprocess_failure( mock_subproc, mock_getpass, mock_input, tmp_path ): mock_input.return_value = '/failinit/repo' mock_getpass.return_value = '' args = make_args(str(tmp_path / 'failinit.rc')) result = run_init(args) assert result == 1 @mock.patch('builtins.input') @mock.patch('getpass.getpass') @mock.patch('os.makedirs', side_effect=Exception('cannot create dir')) def test_run_init_makedirs_exception( mock_makedirs, mock_getpass, mock_input, tmp_path ): mock_input.return_value = '/faildir/repo' mock_getpass.return_value = '' args = make_args(str(tmp_path / 'faildir.rc')) with mock.patch('subprocess.run'): result = run_init(args) assert result == 1 @mock.patch('builtins.input') @mock.patch('getpass.getpass') @mock.patch('os.makedirs') @mock.patch('builtins.open', side_effect=Exception('cannot open file')) def test_run_init_open_exception( mock_open, mock_makedirs, mock_getpass, mock_input, tmp_path ): mock_input.return_value = '/failfile/repo' mock_getpass.return_value = '' args = make_args(str(tmp_path / 'failfile.rc')) with mock.patch('subprocess.run'): result = run_init(args) assert result == 1 @mock.patch('builtins.input') @mock.patch('getpass.getpass') @mock.patch('os.makedirs') @mock.patch('builtins.open') @mock.patch('subprocess.run', side_effect=Exception('unexpected')) def test_run_init_unexpected_exception( mock_subproc, mock_open, mock_makedirs, mock_getpass, mock_input, tmp_path ): mock_input.return_value = '/unexpected/repo' mock_getpass.return_value = '' args = make_args(str(tmp_path / 'unexpected.rc')) result = run_init(args) assert result == 1 @mock.patch('builtins.input', side_effect=KeyboardInterrupt) def test_run_init_keyboard_interrupt(mock_input, tmp_path): args = make_args(str(tmp_path / 'kbint.rc')) result = run_init(args) assert result == 1 @mock.patch('builtins.input') @mock.patch('getpass.getpass') @mock.patch( 'subprocess.run', side_effect=subprocess.CalledProcessError(1, ['borg', 'init']), ) def test_run_init_calledprocesserror( mock_subproc, mock_getpass, mock_input, tmp_path ): mock_input.return_value = '/failsubproc/repo' mock_getpass.return_value = '' args = make_args(str(tmp_path / 'failsubproc.rc')) result = run_init(args) assert result == 1