"""Tests for reading the configuration file and expanding paths""" import configparser import os import pwd from pathlib import Path import pytest from simple_backup import simple_backup as sb USER = pwd.getpwuid(os.getuid()).pw_name HOME = pwd.getpwuid(os.getuid()).pw_dir def write_config(path: Path, text: str) -> str: config_file = path / 'simple_backup.conf' config_file.write_text(text) return str(config_file) def test_repo_copy_imported() -> None: repo = Path(__file__).resolve().parents[1] assert Path(sb.__file__).resolve().is_relative_to(repo) @pytest.mark.parametrize('path, expected', [ ('~', HOME), ('~/docs', f'{HOME}/docs'), ('/abs/~/path', '/abs/~/path'), ('relative', 'relative'), ]) def test_expand_home(path: str, expected: str) -> None: assert sb._expand_home(path, USER) == expected def test_expand_inputs_glob(tmp_path: Path) -> None: for name in ['a.txt', 'b.txt', 'c.log']: (tmp_path / name).touch() result = sb._expand_inputs([f'{tmp_path}/*.txt', ''], USER) assert sorted(result) == [f'{tmp_path}/a.txt', f'{tmp_path}/b.txt'] def test_expand_inputs_no_match(tmp_path: Path, caplog: pytest.LogCaptureFixture) -> None: assert sb._expand_inputs([f'{tmp_path}/missing'], USER) == [] assert 'No file or directory matching input' in caplog.text def test_expand_inputs_tilde_without_user(caplog: pytest.LogCaptureFixture) -> None: sb._expand_inputs(['~/nothing-here'], None) assert 'Cannot expand' in caplog.text def test_missing_config(tmp_path: Path, caplog: pytest.LogCaptureFixture) -> None: config_args = sb._read_config(str(tmp_path / 'missing.conf'), USER) assert config_args['inputs'] is None assert config_args['output'] is None assert config_args['keep'] == -1 assert 'does not exist' in caplog.text def test_missing_config_without_user(tmp_path: Path, caplog: pytest.LogCaptureFixture) -> None: sb._read_config(str(tmp_path / 'missing.conf'), None) assert 'User not specified' in caplog.text def test_read_config(tmp_path: Path) -> None: (tmp_path / 'one').mkdir() (tmp_path / 'two').mkdir() config_file = write_config(tmp_path, f""" [backup] inputs={tmp_path}/one, {tmp_path}/two, {tmp_path}/one backup_dir=~/backups exclude=*.bak, , *.tmp keep=3 """) config_args = sb._read_config(config_file, USER) assert config_args['inputs'] is not None assert sorted(config_args['inputs']) == [f'{tmp_path}/one', f'{tmp_path}/two'] # Expanded later, as it may be a path on the server assert config_args['output'] == '~/backups' assert config_args['exclude'] == ['*.bak', '*.tmp'] assert config_args['keep'] == 3 assert config_args['ssh_host'] is None assert config_args['ssh_user'] is None assert config_args['ssh_keyfile'] is None assert config_args['remote_sudo'] is False assert config_args['numeric_ids'] is False def test_optional_keys_missing(tmp_path: Path) -> None: config_file = write_config(tmp_path, f""" [backup] inputs={tmp_path} backup_dir=/backups """) config_args = sb._read_config(config_file, USER) assert config_args['exclude'] == [] assert config_args['keep'] == -1 def test_old_default_section(tmp_path: Path) -> None: config_file = write_config(tmp_path, f""" [default] inputs={tmp_path} backup_dir=/backups """) config_args = sb._read_config(config_file, USER) assert config_args['inputs'] == [str(tmp_path)] assert config_args['output'] == '/backups' def test_percent_in_values(tmp_path: Path) -> None: config_file = write_config(tmp_path, f""" [backup] inputs={tmp_path} backup_dir=/backups/100% exclude=%temp% """) config_args = sb._read_config(config_file, USER) assert config_args['output'] == '/backups/100%' assert config_args['exclude'] == ['%temp%'] def test_server_section(tmp_path: Path) -> None: config_file = write_config(tmp_path, f""" [backup] inputs={tmp_path} backup_dir=backups [server] ssh_host=server ssh_user=user ssh_keyfile=/keys/id_ed25519 remote_sudo=yes numeric_ids=true """) config_args = sb._read_config(config_file, USER) assert config_args['ssh_host'] == 'server' assert config_args['ssh_user'] == 'user' assert config_args['ssh_keyfile'] == '/keys/id_ed25519' assert config_args['remote_sudo'] is True assert config_args['numeric_ids'] is True def test_empty_server_values(tmp_path: Path) -> None: config_file = write_config(tmp_path, f""" [backup] inputs={tmp_path} backup_dir=/backups [server] ssh_host= ssh_user= ssh_keyfile= remote_sudo= numeric_ids= """) config_args = sb._read_config(config_file, USER) assert config_args['ssh_host'] is None assert config_args['ssh_user'] is None assert config_args['ssh_keyfile'] is None assert config_args['remote_sudo'] is False assert config_args['numeric_ids'] is False def test_only_ssh_host(tmp_path: Path) -> None: config_file = write_config(tmp_path, f""" [backup] inputs={tmp_path} backup_dir=/backups [server] ssh_host=server """) config_args = sb._read_config(config_file, USER) # Kept, so that the missing user is reported instead of silently backing up locally assert config_args['ssh_host'] == 'server' assert config_args['ssh_user'] is None @pytest.mark.parametrize('line', ['keep=abc', 'remote_sudo=maybe']) def test_bad_value(tmp_path: Path, line: str) -> None: section = '[server]' if line.startswith('remote_sudo') else '' config_file = write_config(tmp_path, f""" [backup] inputs={tmp_path} backup_dir=/backups {section} {line} """) with pytest.raises(ValueError): sb._read_config(config_file, USER) @pytest.mark.parametrize('text', [ '[backup]\nbackup_dir=/backups\n', '[backup]\ninputs=/tmp\n', '[other]\ninputs=/tmp\n', 'inputs=/tmp\n', ]) def test_bad_config(tmp_path: Path, text: str) -> None: config_file = write_config(tmp_path, text) with pytest.raises(configparser.Error): sb._read_config(config_file, USER)