diff --git a/tests/cli/conftest.py b/tests/cli/conftest.py new file mode 100644 index 0000000..3d925b1 --- /dev/null +++ b/tests/cli/conftest.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- + +from textwrap import dedent + +from click.testing import CliRunner + +import pytest + +import vdirsyncer.cli as cli + + +class _CustomRunner(object): + def __init__(self, tmpdir): + self.tmpdir = tmpdir + self.cfg = tmpdir.join('config') + self.runner = CliRunner() + + def invoke(self, args, env=None, **kwargs): + env = env or {} + env.setdefault('VDIRSYNCER_CONFIG', str(self.cfg)) + return self.runner.invoke(cli.app, args, env=env, **kwargs) + + def write_with_general(self, data): + self.cfg.write(dedent(''' + [general] + status_path = {}/status/ + ''').format(str(self.tmpdir))) + self.cfg.write(data, mode='a') + + +@pytest.fixture +def runner(tmpdir): + return _CustomRunner(tmpdir) diff --git a/tests/cli/test_config.py b/tests/cli/test_config.py new file mode 100644 index 0000000..fc9756d --- /dev/null +++ b/tests/cli/test_config.py @@ -0,0 +1,158 @@ +import io +from textwrap import dedent + +import pytest + +from vdirsyncer import cli + + +def test_read_config(monkeypatch): + f = io.StringIO(dedent(u''' + [general] + status_path = /tmp/status/ + + [pair bob] + a = bob_a + b = bob_b + foo = bar + bam = true + + [storage bob_a] + type = filesystem + path = /tmp/contacts/ + fileext = .vcf + yesno = false + number = 42 + + [storage bob_b] + type = carddav + + [bogus] + lol = true + ''')) + + errors = [] + monkeypatch.setattr('vdirsyncer.cli.cli_logger.error', errors.append) + general, pairs, storages = cli.utils.read_config(f) + assert general == {'status_path': '/tmp/status/'} + assert pairs == {'bob': ('bob_a', 'bob_b', {'bam': True, 'foo': 'bar'})} + assert storages == { + 'bob_a': {'type': 'filesystem', 'path': '/tmp/contacts/', 'fileext': + '.vcf', 'yesno': False, 'number': 42, + 'instance_name': 'bob_a'}, + 'bob_b': {'type': 'carddav', 'instance_name': 'bob_b'} + } + + assert len(errors) == 1 + assert errors[0].startswith('Unknown section') + assert 'bogus' in errors[0] + + +def test_storage_instance_from_config(monkeypatch): + def lol(**kw): + assert kw == {'foo': 'bar', 'baz': 1} + return 'OK' + + import vdirsyncer.storage + monkeypatch.setitem(vdirsyncer.storage.storage_names, 'lol', lol) + config = {'type': 'lol', 'foo': 'bar', 'baz': 1} + assert cli.utils.storage_instance_from_config(config) == 'OK' + + +def test_parse_pairs_args(): + pairs = { + 'foo': ('bar', 'baz', {'conflict_resolution': 'a wins'}, + {'storage_option': True}), + 'one': ('two', 'three', {'collections': 'a,b,c'}, {}), + 'eins': ('zwei', 'drei', {'ha': True}, {}) + } + assert sorted( + cli.parse_pairs_args(['foo/foocoll', 'one', 'eins'], pairs) + ) == [ + ('eins', set()), + ('foo', {'foocoll'}), + ('one', set()), + ] + + +def test_missing_general_section(tmpdir, runner): + runner.cfg.write(dedent(''' + [pair my_pair] + a = my_a + b = my_b + + [storage my_a] + type = filesystem + path = {0}/path_a/ + fileext = .txt + + [storage my_b] + type = filesystem + path = {0}/path_b/ + fileext = .txt + ''').format(str(tmpdir))) + + result = runner.invoke(['sync']) + assert result.exception + assert result.output.startswith('critical:') + assert 'invalid general section' in result.output.lower() + + +def test_wrong_general_section(tmpdir, runner): + runner.cfg.write(dedent(''' + [general] + wrong = true + ''')) + result = runner.invoke(['sync']) + + assert result.exception + lines = result.output.splitlines() + assert lines[:-2] == [ + 'critical: general section doesn\'t take the parameters: wrong', + 'critical: general section is missing the parameters: status_path' + ] + assert 'Invalid general section.' in lines[-2] + + +def test_invalid_storage_name(): + f = io.StringIO(dedent(u''' + [general] + status_path = /tmp/status/ + + [storage foo.bar] + ''')) + + with pytest.raises(cli.CliError) as excinfo: + cli.utils.read_config(f) + + assert 'invalid characters' in str(excinfo.value).lower() + + +def test_parse_config_value(capsys): + invalid = object() + + def x(s): + try: + rv = cli.utils.parse_config_value(s) + except ValueError: + return invalid + else: + warnings = capsys.readouterr()[1] + return rv, len(warnings.splitlines()) + + assert x('123 # comment!') is invalid + + assert x('True') == ('True', 1) + assert x('False') == ('False', 1) + assert x('Yes') == ('Yes', 1) + assert x('None') == ('None', 1) + assert x('"True"') == ('True', 0) + assert x('"False"') == ('False', 0) + + assert x('"123 # comment!"') == ('123 # comment!', 0) + assert x('true') == (True, 0) + assert x('false') == (False, 0) + assert x('null') == (None, 0) + assert x('3.14') == (3.14, 0) + assert x('') == ('', 0) + assert x('""') == ('', 0) diff --git a/tests/cli/test_discover.py b/tests/cli/test_discover.py new file mode 100644 index 0000000..3b2a5d8 --- /dev/null +++ b/tests/cli/test_discover.py @@ -0,0 +1,55 @@ +from textwrap import dedent + + +def test_discover_command(tmpdir, runner): + runner.write_with_general(dedent(''' + [storage foo] + type = filesystem + path = {0}/foo/ + fileext = .txt + + [storage bar] + type = filesystem + path = {0}/bar/ + fileext = .txt + + [pair foobar] + a = foo + b = bar + collections = ["from a"] + ''').format(str(tmpdir))) + + foo = tmpdir.mkdir('foo') + bar = tmpdir.mkdir('bar') + + for x in 'abc': + foo.mkdir(x) + bar.mkdir(x) + bar.mkdir('d') + + result = runner.invoke(['sync']) + assert not result.exception + lines = result.output.splitlines() + assert lines[0].startswith('Discovering') + assert 'Syncing foobar/a' in lines + assert 'Syncing foobar/b' in lines + assert 'Syncing foobar/c' in lines + assert 'Syncing foobar/d' not in lines + + foo.mkdir('d') + result = runner.invoke(['sync']) + assert not result.exception + assert 'Syncing foobar/a' in lines + assert 'Syncing foobar/b' in lines + assert 'Syncing foobar/c' in lines + assert 'Syncing foobar/d' not in result.output + + result = runner.invoke(['discover']) + assert not result.exception + + result = runner.invoke(['sync']) + assert not result.exception + assert 'Syncing foobar/a' in lines + assert 'Syncing foobar/b' in lines + assert 'Syncing foobar/c' in lines + assert 'Syncing foobar/d' in result.output diff --git a/tests/test_cli.py b/tests/cli/test_main.py similarity index 56% rename from tests/test_cli.py rename to tests/cli/test_main.py index c396283..eea5b56 100644 --- a/tests/test_cli.py +++ b/tests/cli/test_main.py @@ -1,108 +1,12 @@ # -*- coding: utf-8 -*- -import io from textwrap import dedent from click.testing import CliRunner -import pytest - import vdirsyncer.cli as cli -class _CustomRunner(object): - def __init__(self, tmpdir): - self.tmpdir = tmpdir - self.cfg = tmpdir.join('config') - self.runner = CliRunner() - - def invoke(self, args, env=None, **kwargs): - env = env or {} - env.setdefault('VDIRSYNCER_CONFIG', str(self.cfg)) - return self.runner.invoke(cli.app, args, env=env, **kwargs) - - def write_with_general(self, data): - self.cfg.write(dedent(''' - [general] - status_path = {}/status/ - ''').format(str(self.tmpdir))) - self.cfg.write(data, mode='a') - - -@pytest.fixture -def runner(tmpdir, monkeypatch): - return _CustomRunner(tmpdir) - - -def test_read_config(monkeypatch): - f = io.StringIO(dedent(u''' - [general] - status_path = /tmp/status/ - - [pair bob] - a = bob_a - b = bob_b - foo = bar - bam = true - - [storage bob_a] - type = filesystem - path = /tmp/contacts/ - fileext = .vcf - yesno = false - number = 42 - - [storage bob_b] - type = carddav - - [bogus] - lol = true - ''')) - - errors = [] - monkeypatch.setattr('vdirsyncer.cli.cli_logger.error', errors.append) - general, pairs, storages = cli.utils.read_config(f) - assert general == {'status_path': '/tmp/status/'} - assert pairs == {'bob': ('bob_a', 'bob_b', {'bam': True, 'foo': 'bar'})} - assert storages == { - 'bob_a': {'type': 'filesystem', 'path': '/tmp/contacts/', 'fileext': - '.vcf', 'yesno': False, 'number': 42, - 'instance_name': 'bob_a'}, - 'bob_b': {'type': 'carddav', 'instance_name': 'bob_b'} - } - - assert len(errors) == 1 - assert errors[0].startswith('Unknown section') - assert 'bogus' in errors[0] - - -def test_storage_instance_from_config(monkeypatch): - def lol(**kw): - assert kw == {'foo': 'bar', 'baz': 1} - return 'OK' - - import vdirsyncer.storage - monkeypatch.setitem(vdirsyncer.storage.storage_names, 'lol', lol) - config = {'type': 'lol', 'foo': 'bar', 'baz': 1} - assert cli.utils.storage_instance_from_config(config) == 'OK' - - -def test_parse_pairs_args(): - pairs = { - 'foo': ('bar', 'baz', {'conflict_resolution': 'a wins'}, - {'storage_option': True}), - 'one': ('two', 'three', {'collections': 'a,b,c'}, {}), - 'eins': ('zwei', 'drei', {'ha': True}, {}) - } - assert sorted( - cli.parse_pairs_args(['foo/foocoll', 'one', 'eins'], pairs) - ) == [ - ('eins', set()), - ('foo', {'foocoll'}), - ('one', set()), - ] - - def test_simple_run(tmpdir, runner): runner.write_with_general(dedent(''' [pair my_pair] @@ -167,45 +71,6 @@ def test_empty_storage(tmpdir, runner): assert result.exception -def test_missing_general_section(tmpdir, runner): - runner.cfg.write(dedent(''' - [pair my_pair] - a = my_a - b = my_b - - [storage my_a] - type = filesystem - path = {0}/path_a/ - fileext = .txt - - [storage my_b] - type = filesystem - path = {0}/path_b/ - fileext = .txt - ''').format(str(tmpdir))) - - result = runner.invoke(['sync']) - assert result.exception - assert result.output.startswith('critical:') - assert 'invalid general section' in result.output.lower() - - -def test_wrong_general_section(tmpdir, runner): - runner.cfg.write(dedent(''' - [general] - wrong = true - ''')) - result = runner.invoke(['sync']) - - assert result.exception - lines = result.output.splitlines() - assert lines[:-2] == [ - 'critical: general section doesn\'t take the parameters: wrong', - 'critical: general section is missing the parameters: status_path' - ] - assert 'Invalid general section.' in lines[-2] - - def test_verbosity(tmpdir): runner = CliRunner() config_file = tmpdir.join('config') @@ -219,20 +84,6 @@ def test_verbosity(tmpdir): assert 'invalid verbosity value' in result.output.lower() -def test_invalid_storage_name(): - f = io.StringIO(dedent(u''' - [general] - status_path = /tmp/status/ - - [storage foo.bar] - ''')) - - with pytest.raises(cli.CliError) as excinfo: - cli.utils.read_config(f) - - assert 'invalid characters' in str(excinfo.value).lower() - - def test_deprecated_item_status(tmpdir): f = tmpdir.join('mypair.items') f.write(dedent(''' @@ -348,60 +199,6 @@ def test_invalid_pairs_as_cli_arg(tmpdir, runner): assert 'pair foobar: collection d not found' in result.output.lower() -def test_discover_command(tmpdir, runner): - runner.write_with_general(dedent(''' - [storage foo] - type = filesystem - path = {0}/foo/ - fileext = .txt - - [storage bar] - type = filesystem - path = {0}/bar/ - fileext = .txt - - [pair foobar] - a = foo - b = bar - collections = ["from a"] - ''').format(str(tmpdir))) - - foo = tmpdir.mkdir('foo') - bar = tmpdir.mkdir('bar') - - for x in 'abc': - foo.mkdir(x) - bar.mkdir(x) - bar.mkdir('d') - - result = runner.invoke(['sync']) - assert not result.exception - lines = result.output.splitlines() - assert lines[0].startswith('Discovering') - assert 'Syncing foobar/a' in lines - assert 'Syncing foobar/b' in lines - assert 'Syncing foobar/c' in lines - assert 'Syncing foobar/d' not in lines - - foo.mkdir('d') - result = runner.invoke(['sync']) - assert not result.exception - assert 'Syncing foobar/a' in lines - assert 'Syncing foobar/b' in lines - assert 'Syncing foobar/c' in lines - assert 'Syncing foobar/d' not in result.output - - result = runner.invoke(['discover']) - assert not result.exception - - result = runner.invoke(['sync']) - assert not result.exception - assert 'Syncing foobar/a' in lines - assert 'Syncing foobar/b' in lines - assert 'Syncing foobar/c' in lines - assert 'Syncing foobar/d' in result.output - - def test_multiple_pairs(tmpdir, runner): def get_cfg(): for name_a, name_b in ('foo', 'bar'), ('bam', 'baz'): @@ -520,33 +317,3 @@ def test_ident_conflict(tmpdir, runner): 'two.txt' in result.output, 'three.txt' in result.output, ]) == [False, True, True] - - -def test_parse_config_value(capsys): - invalid = object() - - def x(s): - try: - rv = cli.utils.parse_config_value(s) - except ValueError: - return invalid - else: - warnings = capsys.readouterr()[1] - return rv, len(warnings.splitlines()) - - assert x('123 # comment!') is invalid - - assert x('True') == ('True', 1) - assert x('False') == ('False', 1) - assert x('Yes') == ('Yes', 1) - assert x('None') == ('None', 1) - assert x('"True"') == ('True', 0) - assert x('"False"') == ('False', 0) - - assert x('"123 # comment!"') == ('123 # comment!', 0) - assert x('true') == (True, 0) - assert x('false') == (False, 0) - assert x('null') == (None, 0) - assert x('3.14') == (3.14, 0) - assert x('') == ('', 0) - assert x('""') == ('', 0)