1# Copyright 2014-2016 Presslabs SRL
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#    http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14from _pygit2 import GitError
15
16from unittest.mock import MagicMock, patch, call
17from six.moves.queue import Empty
18import pygit2
19import pytest
20
21from gitfs.worker.sync import SyncWorker
22
23
24class TestSyncWorker(object):
25    def test_work(self):
26        mocked_queue = MagicMock()
27        mocked_idle = MagicMock(side_effect=ValueError)
28
29        mocked_queue.get.side_effect = Empty()
30
31        worker = SyncWorker(
32            "name",
33            "email",
34            "name",
35            "email",
36            strategy="strategy",
37            commit_queue=mocked_queue,
38        )
39        worker.on_idle = mocked_idle
40        worker.timeout = 1
41        worker.min_idle_times = 1
42
43        with pytest.raises(ValueError):
44            worker.work()
45
46        mocked_queue.get.assert_called_once_with(timeout=1, block=True)
47        assert mocked_idle.call_count == 1
48
49    def test_on_idle_with_commits_and_merges(self):
50        mocked_sync = MagicMock()
51        mocked_syncing = MagicMock()
52        mocked_commit = MagicMock()
53
54        mocked_syncing.is_set.return_value = False
55
56        with patch.multiple(
57            "gitfs.worker.sync", syncing=mocked_syncing, writers=MagicMock(value=0)
58        ):
59            worker = SyncWorker("name", "email", "name", "email", strategy="strategy")
60            worker.commits = "commits"
61            worker.commit = mocked_commit
62            worker.sync = mocked_sync
63
64            commits = worker.on_idle()
65
66            mocked_commit.assert_called_once_with("commits")
67            assert mocked_syncing.set.call_count == 1
68            assert mocked_sync.call_count == 1
69            assert commits is None
70
71    def test_merge(self):
72        mocked_strategy = MagicMock()
73        mocked_repo = MagicMock()
74        upstream = "origin"
75        branch = "master"
76
77        worker = SyncWorker(
78            "name",
79            "email",
80            "name",
81            "email",
82            strategy=mocked_strategy,
83            repository=mocked_repo,
84            upstream=upstream,
85            branch=branch,
86        )
87        worker.merge()
88
89        mocked_strategy.assert_called_once_with(branch, branch, upstream)
90        assert mocked_repo.commits.update.call_count == 1
91
92    def test_sync(self):
93        upstream = "origin"
94        branch = "master"
95        credentials = "credentials"
96        mocked_repo = MagicMock()
97        mocked_merge = MagicMock()
98        mocked_sync_done = MagicMock()
99        mocked_syncing = MagicMock()
100        mocked_push_successful = MagicMock()
101        mocked_fetch = MagicMock()
102        mocked_strategy = MagicMock()
103
104        mocked_repo.behind = True
105        mocked_push_successful.set.side_effect = ValueError
106
107        with patch.multiple(
108            "gitfs.worker.sync",
109            sync_done=mocked_sync_done,
110            syncing=mocked_syncing,
111            push_successful=mocked_push_successful,
112            fetch=mocked_fetch,
113        ):
114            worker = SyncWorker(
115                "name",
116                "email",
117                "name",
118                "email",
119                repository=mocked_repo,
120                strategy=mocked_strategy,
121                credentials=credentials,
122                upstream=upstream,
123                branch=branch,
124            )
125            worker.merge = mocked_merge
126
127            worker.sync()
128
129            assert mocked_syncing.clear.call_count == 1
130            assert mocked_push_successful.clear.call_count == 1
131            assert mocked_sync_done.clear.call_count == 1
132            assert mocked_sync_done.set.call_count == 1
133            assert mocked_fetch.set.call_count == 1
134            assert mocked_push_successful.set.call_count == 1
135            assert mocked_repo.behind is False
136            mocked_repo.push.assert_called_once_with(upstream, branch, credentials)
137
138    def test_sync_with_push_conflict(self):
139        upstream = "origin"
140        branch = "master"
141        credentials = "credentials"
142        mocked_repo = MagicMock()
143        mocked_merge = MagicMock()
144        mocked_sync_done = MagicMock()
145        mocked_syncing = MagicMock()
146        mocked_push_successful = MagicMock()
147        mocked_fetch = MagicMock()
148        mocked_strategy = MagicMock()
149
150        mocked_repo.behind = True
151        mocked_repo.ahead = MagicMock(1)
152        mocked_repo.push.side_effect = [GitError("Mocked error"), None]
153
154        with patch.multiple(
155            "gitfs.worker.sync",
156            sync_done=mocked_sync_done,
157            syncing=mocked_syncing,
158            push_successful=mocked_push_successful,
159            fetch=mocked_fetch,
160        ):
161            worker = SyncWorker(
162                "name",
163                "email",
164                "name",
165                "email",
166                repository=mocked_repo,
167                strategy=mocked_strategy,
168                credentials=credentials,
169                upstream=upstream,
170                branch=branch,
171            )
172            worker.merge = mocked_merge
173
174            while not worker.sync():
175                pass
176
177            assert mocked_syncing.clear.call_count == 1
178            assert mocked_push_successful.clear.call_count == 1
179            assert mocked_sync_done.clear.call_count == 2
180            assert mocked_sync_done.set.call_count == 1
181            assert mocked_fetch.set.call_count == 1
182            assert mocked_push_successful.set.call_count == 1
183            assert mocked_repo.behind is False
184            assert mocked_repo.ahead.call_count == 2
185
186            mocked_repo.push.assert_has_calls(
187                [
188                    call(upstream, branch, credentials),
189                    call(upstream, branch, credentials),
190                ]
191            )
192
193    def test_commit_with_just_one_job(self):
194        mocked_repo = MagicMock()
195
196        message = "just a simple message"
197        jobs = [{"params": {"message": message}}]
198        author = ("name", "email")
199
200        worker = SyncWorker(
201            author[0],
202            author[1],
203            author[0],
204            author[1],
205            strategy="strategy",
206            repository=mocked_repo,
207        )
208        worker.commit(jobs)
209
210        mocked_repo.commit.assert_called_once_with(message, author, author)
211        assert mocked_repo.commits.update.call_count == 1
212
213        strategy = pygit2.GIT_CHECKOUT_FORCE
214        mocked_repo.checkout_head.assert_called_once_with(strategy=strategy)
215
216    def test_commit_with_more_than_one_job(self):
217        mocked_repo = MagicMock()
218
219        message = "just a simple message"
220        jobs = [
221            {"params": {"message": message, "add": ["path1", "path2"], "remove": []}},
222            {"params": {"message": message, "remove": ["path2"], "add": []}},
223        ]
224        author = ("name", "email")
225
226        worker = SyncWorker(
227            author[0],
228            author[1],
229            author[0],
230            author[1],
231            strategy="strategy",
232            repository=mocked_repo,
233        )
234        worker.commit(jobs)
235
236        asserted_message = "Update 2 items. Added 2 items. Removed 1 items."
237        mocked_repo.commit.assert_called_once_with(asserted_message, author, author)
238        assert mocked_repo.commits.update.call_count == 1
239
240        strategy = pygit2.GIT_CHECKOUT_FORCE
241        mocked_repo.checkout_head.assert_called_once_with(strategy=strategy)
242
243    def test_switch_to_idle_mode(self):
244        mocked_queue = MagicMock()
245        mocked_idle = MagicMock(side_effect=ValueError)
246        mocked_idle_event = MagicMock()
247
248        mocked_queue.get.side_effect = Empty()
249
250        with patch.multiple("gitfs.worker.sync", idle=mocked_idle_event):
251            worker = SyncWorker(
252                "name",
253                "email",
254                "name",
255                "email",
256                strategy="strategy",
257                commit_queue=mocked_queue,
258            )
259            worker.on_idle = mocked_idle
260            worker.timeout = 1
261            worker.min_idle_times = -1
262
263            with pytest.raises(ValueError):
264                worker.work()
265
266            mocked_queue.get.assert_called_once_with(timeout=1, block=True)
267            assert mocked_idle_event.set.call_count == 1
268            assert mocked_idle.call_count == 1
269