1from contextlib import contextmanager
2from unittest.mock import patch
3
4import hypothesis.strategies as st
5import pytest
6from hypothesis import given
7
8from vdirsyncer import exceptions
9from vdirsyncer.cli.fetchparams import expand_fetch_params
10from vdirsyncer.cli.fetchparams import STRATEGIES
11
12
13@pytest.fixture
14def mystrategy(monkeypatch):
15    def strategy(x):
16        calls.append(x)
17        return x
18
19    calls = []
20    monkeypatch.setitem(STRATEGIES, "mystrategy", strategy)
21    return calls
22
23
24@contextmanager
25def dummy_strategy():
26    def strategy(x):
27        calls.append(x)
28        return x
29
30    calls = []
31    with patch.dict(STRATEGIES, {"mystrategy": strategy}):
32        yield calls
33
34
35@pytest.fixture
36def value_cache(monkeypatch):
37    _cache = {}
38
39    class FakeContext:
40        fetched_params = _cache
41
42        def find_object(self, _):
43            return self
44
45    def get_context(*a, **kw):
46        return FakeContext()
47
48    monkeypatch.setattr("click.get_current_context", get_context)
49    return _cache
50
51
52def test_key_conflict(monkeypatch, mystrategy):
53    with pytest.raises(ValueError) as excinfo:
54        expand_fetch_params({"foo": "bar", "foo.fetch": ["mystrategy", "baz"]})
55
56    assert "Can't set foo.fetch and foo." in str(excinfo.value)
57
58
59@given(s=st.text(), t=st.text(min_size=1))
60def test_fuzzing(s, t):
61    with dummy_strategy():
62        config = expand_fetch_params({f"{s}.fetch": ["mystrategy", t]})
63
64    assert config[s] == t
65
66
67@pytest.mark.parametrize("value", [[], "lol", 42])
68def test_invalid_fetch_value(mystrategy, value):
69    with pytest.raises(ValueError) as excinfo:
70        expand_fetch_params({"foo.fetch": value})
71
72    assert "Expected a list" in str(
73        excinfo.value
74    ) or "Expected list of length > 0" in str(excinfo.value)
75
76
77def test_unknown_strategy():
78    with pytest.raises(exceptions.UserError) as excinfo:
79        expand_fetch_params({"foo.fetch": ["unreal", "asdf"]})
80
81    assert "Unknown strategy" in str(excinfo.value)
82
83
84def test_caching(monkeypatch, mystrategy, value_cache):
85    orig_cfg = {"foo.fetch": ["mystrategy", "asdf"]}
86
87    rv = expand_fetch_params(orig_cfg)
88    assert rv["foo"] == "asdf"
89    assert mystrategy == ["asdf"]
90    assert len(value_cache) == 1
91
92    rv = expand_fetch_params(orig_cfg)
93    assert rv["foo"] == "asdf"
94    assert mystrategy == ["asdf"]
95    assert len(value_cache) == 1
96
97    value_cache.clear()
98    rv = expand_fetch_params(orig_cfg)
99    assert rv["foo"] == "asdf"
100    assert mystrategy == ["asdf"] * 2
101    assert len(value_cache) == 1
102
103
104def test_failed_strategy(monkeypatch, value_cache):
105    calls = []
106
107    def strategy(x):
108        calls.append(x)
109        raise KeyboardInterrupt()
110
111    monkeypatch.setitem(STRATEGIES, "mystrategy", strategy)
112
113    orig_cfg = {"foo.fetch": ["mystrategy", "asdf"]}
114
115    for _ in range(2):
116        with pytest.raises(KeyboardInterrupt):
117            expand_fetch_params(orig_cfg)
118
119    assert len(value_cache) == 1
120    assert len(calls) == 1
121
122
123def test_empty_value(monkeypatch, mystrategy):
124    with pytest.raises(exceptions.UserError) as excinfo:
125        expand_fetch_params({"foo.fetch": ["mystrategy", ""]})
126
127    assert "Empty value for foo.fetch, this most likely indicates an error" in str(
128        excinfo.value
129    )
130