vdirsyncer/tests/system/cli/test_config.py
Sami Samhuri cbb4e314f6 cli/discover: add implicit config to pair for collection creation
Adds support for auto-creating collections when they exist only on
one side and `implicit = 'create'` is set in the pair config.
2025-11-06 00:04:22 +00:00

283 lines
6.4 KiB
Python

from __future__ import annotations
import io
from textwrap import dedent
import pytest
from vdirsyncer import cli
from vdirsyncer import exceptions
from vdirsyncer.cli.config import Config
invalid = object()
@pytest.fixture
def read_config(tmpdir, monkeypatch):
def inner(cfg):
errors = []
monkeypatch.setattr("vdirsyncer.cli.cli_logger.error", errors.append)
f = io.StringIO(dedent(cfg.format(base=str(tmpdir))))
rv = Config.from_fileobject(f)
monkeypatch.undo()
return errors, rv
return inner
def test_read_config(read_config):
errors, c = read_config(
"""
[general]
status_path = "/tmp/status/"
[pair bob]
a = "bob_a"
b = "bob_b"
collections = null
[storage bob_a]
type = "filesystem"
path = "/tmp/contacts/"
fileext = ".vcf"
yesno = false
number = 42
[storage bob_b]
type = "carddav"
"""
)
assert c.general == {"status_path": "/tmp/status/"}
assert set(c.pairs) == {"bob"}
bob = c.pairs["bob"]
assert bob.collections is None
assert c.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"},
}
def test_missing_collections_param(read_config):
with pytest.raises(exceptions.UserError) as excinfo:
read_config(
"""
[general]
status_path = "/tmp/status/"
[pair bob]
a = "bob_a"
b = "bob_b"
[storage bob_a]
type = "lmao"
[storage bob_b]
type = "lmao"
"""
)
assert "collections parameter missing" in str(excinfo.value)
def test_invalid_section_type(read_config):
with pytest.raises(exceptions.UserError) as excinfo:
read_config(
"""
[general]
status_path = "/tmp/status/"
[bogus]
"""
)
assert "Unknown section" in str(excinfo.value)
assert "bogus" in str(excinfo.value)
def test_missing_general_section(read_config):
with pytest.raises(exceptions.UserError) as excinfo:
read_config(
"""
[pair my_pair]
a = "my_a"
b = "my_b"
collections = null
[storage my_a]
type = "filesystem"
path = "{base}/path_a/"
fileext = ".txt"
[storage my_b]
type = "filesystem"
path = "{base}/path_b/"
fileext = ".txt"
"""
)
assert "Invalid general section." in str(excinfo.value)
def test_wrong_general_section(read_config):
with pytest.raises(exceptions.UserError) as excinfo:
read_config(
"""
[general]
wrong = true
"""
)
assert "Invalid general section." in str(excinfo.value)
assert excinfo.value.problems == [
"general section doesn't take the parameters: wrong",
"general section is missing the parameters: status_path",
]
def test_invalid_storage_name(read_config):
with pytest.raises(exceptions.UserError) as excinfo:
read_config(
"""
[general]
status_path = "{base}/status/"
[storage foo.bar]
"""
)
assert "invalid characters" in str(excinfo.value).lower()
def test_invalid_collections_arg(read_config):
with pytest.raises(exceptions.UserError) as excinfo:
read_config(
"""
[general]
status_path = "/tmp/status/"
[pair foobar]
a = "foo"
b = "bar"
collections = [null]
[storage foo]
type = "filesystem"
path = "/tmp/foo/"
fileext = ".txt"
[storage bar]
type = "filesystem"
path = "/tmp/bar/"
fileext = ".txt"
"""
)
assert "Expected string" in str(excinfo.value)
def test_duplicate_sections(read_config):
with pytest.raises(exceptions.UserError) as excinfo:
read_config(
"""
[general]
status_path = "/tmp/status/"
[pair foobar]
a = "foobar"
b = "bar"
collections = null
[storage foobar]
type = "filesystem"
path = "/tmp/foo/"
fileext = ".txt"
[storage bar]
type = "filesystem"
path = "/tmp/bar/"
fileext = ".txt"
"""
)
assert 'Name "foobar" already used' in str(excinfo.value)
def test_validate_collections_param():
x = cli.config._validate_collections_param
x(None)
x(["c", "a", "b"])
pytest.raises(ValueError, x, [None])
pytest.raises(ValueError, x, ["a", "a", "a"])
pytest.raises(ValueError, x, [[None, "a", "b"]])
x([["c", None, "b"]])
x([["c", "a", None]])
x([["c", None, None]])
def test_invalid_implicit_value(read_config):
expected_message = "`implicit` parameter must be 'create' or absent"
with pytest.raises(exceptions.UserError) as excinfo:
read_config(
"""
[general]
status_path = "/tmp/status/"
[pair my_pair]
a = "my_a"
b = "my_b"
collections = null
implicit = "invalid"
[storage my_a]
type = "filesystem"
path = "{base}/path_a/"
fileext = ".txt"
[storage my_b]
type = "filesystem"
path = "{base}/path_b/"
fileext = ".txt"
"""
)
assert expected_message in str(excinfo.value)
def test_implicit_create_only(read_config):
"""Test that implicit create works."""
errors, c = read_config(
"""
[general]
status_path = "/tmp/status/"
[pair my_pair]
a = "my_a"
b = "my_b"
collections = ["from a", "from b"]
implicit = "create"
[storage my_a]
type = "filesystem"
path = "{base}/path_a/"
fileext = ".txt"
[storage my_b]
type = "filesystem"
path = "{base}/path_b/"
fileext = ".txt"
"""
)
assert not errors
pair = c.pairs["my_pair"]
assert pair.implicit == "create"