Files
locutus/test/test_init.py
Alexander Wainwright f79664c5e5 Initial commit
2025-06-24 17:46:38 +10:00

170 lines
4.8 KiB
Python

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