[tx-robot] updated from transifex
[pub/Android/ownCloud.git] / third_party / transifex-client / tests / test_project.py
1 # -*- coding: utf-8 -*-
2
3 from __future__ import with_statement
4 import unittest
5 import contextlib
6 import itertools
7 try:
8 import json
9 except ImportError:
10 import simplejson as json
11 from mock import Mock, patch
12
13 from txclib.project import Project
14 from txclib.config import Flipdict
15
16
17 class TestProject(unittest.TestCase):
18
19 def test_extract_fields(self):
20 """Test the functions that extract a field from a stats object."""
21 stats = {
22 'completed': '80%',
23 'last_update': '00:00',
24 'foo': 'bar',
25 }
26 self.assertEqual(
27 stats['completed'], '%s%%' % Project._extract_completed(stats)
28 )
29 self.assertEqual(stats['last_update'], Project._extract_updated(stats))
30
31 def test_specifying_resources(self):
32 """Test the various ways to specify resources in a project."""
33 p = Project(init=False)
34 resources = [
35 'proj1.res1',
36 'proj2.res2',
37 'transifex.txn',
38 'transifex.txo',
39 ]
40 with patch.object(p, 'get_resource_list') as mock:
41 mock.return_value = resources
42 cmd_args = [
43 'proj1.res1', '*1*', 'transifex*', '*r*',
44 '*o', 'transifex.tx?', 'transifex.txn',
45 ]
46 results = [
47 ['proj1.res1', ],
48 ['proj1.res1', ],
49 ['transifex.txn', 'transifex.txo', ],
50 ['proj1.res1', 'proj2.res2', 'transifex.txn', 'transifex.txo', ],
51 ['transifex.txo', ],
52 ['transifex.txn', 'transifex.txo', ],
53 ['transifex.txn', ],
54 [],
55 ]
56
57 for i, arg in enumerate(cmd_args):
58 resources = [arg]
59 self.assertEqual(p.get_chosen_resources(resources), results[i])
60
61 # wrong argument
62 resources = ['*trasnifex*', ]
63 self.assertRaises(Exception, p.get_chosen_resources, resources)
64
65
66 class TestProjectMinimumPercent(unittest.TestCase):
67 """Test the minimum-perc option."""
68
69 def setUp(self):
70 super(TestProjectMinimumPercent, self).setUp()
71 self.p = Project(init=False)
72 self.p.minimum_perc = None
73 self.p.resource = "resource"
74
75 def test_cmd_option(self):
76 """Test command-line option."""
77 self.p.minimum_perc = 20
78 results = itertools.cycle([80, 90 ])
79 def side_effect(*args):
80 return results.next()
81
82 with patch.object(self.p, "get_resource_option") as mock:
83 mock.side_effect = side_effect
84 self.assertFalse(self.p._satisfies_min_translated({'completed': '12%'}))
85 self.assertTrue(self.p._satisfies_min_translated({'completed': '20%'}))
86 self.assertTrue(self.p._satisfies_min_translated({'completed': '30%'}))
87
88 def test_global_only(self):
89 """Test only global option."""
90 results = itertools.cycle([80, None ])
91 def side_effect(*args):
92 return results.next()
93
94 with patch.object(self.p, "get_resource_option") as mock:
95 mock.side_effect = side_effect
96 self.assertFalse(self.p._satisfies_min_translated({'completed': '70%'}))
97 self.assertTrue(self.p._satisfies_min_translated({'completed': '80%'}))
98 self.assertTrue(self.p._satisfies_min_translated({'completed': '90%'}))
99
100 def test_local_lower_than_global(self):
101 """Test the case where the local option is lower than the global."""
102 results = itertools.cycle([80, 70 ])
103 def side_effect(*args):
104 return results.next()
105
106 with patch.object(self.p, "get_resource_option") as mock:
107 mock.side_effect = side_effect
108 self.assertFalse(self.p._satisfies_min_translated({'completed': '60%'}))
109 self.assertTrue(self.p._satisfies_min_translated({'completed': '70%'}))
110 self.assertTrue(self.p._satisfies_min_translated({'completed': '80%'}))
111 self.assertTrue(self.p._satisfies_min_translated({'completed': '90%'}))
112
113 def test_local_higher_than_global(self):
114 """Test the case where the local option is lower than the global."""
115 results = itertools.cycle([60, 70 ])
116 def side_effect(*args):
117 return results.next()
118
119 with patch.object(self.p, "get_resource_option") as mock:
120 mock.side_effect = side_effect
121 self.assertFalse(self.p._satisfies_min_translated({'completed': '60%'}))
122 self.assertTrue(self.p._satisfies_min_translated({'completed': '70%'}))
123 self.assertTrue(self.p._satisfies_min_translated({'completed': '80%'}))
124 self.assertTrue(self.p._satisfies_min_translated({'completed': '90%'}))
125
126 def test_local_only(self):
127 """Test the case where the local option is lower than the global."""
128 results = itertools.cycle([None, 70 ])
129 def side_effect(*args):
130 return results.next()
131
132 with patch.object(self.p, "get_resource_option") as mock:
133 mock.side_effect = side_effect
134 self.assertFalse(self.p._satisfies_min_translated({'completed': '60%'}))
135 self.assertTrue(self.p._satisfies_min_translated({'completed': '70%'}))
136 self.assertTrue(self.p._satisfies_min_translated({'completed': '80%'}))
137 self.assertTrue(self.p._satisfies_min_translated({'completed': '90%'}))
138
139 def test_no_option(self):
140 """"Test the case there is nothing defined."""
141 results = itertools.cycle([None, None ])
142 def side_effect(*args):
143 return results.next()
144
145 with patch.object(self.p, "get_resource_option") as mock:
146 mock.side_effect = side_effect
147 self.assertTrue(self.p._satisfies_min_translated({'completed': '0%'}))
148 self.assertTrue(self.p._satisfies_min_translated({'completed': '10%'}))
149 self.assertTrue(self.p._satisfies_min_translated({'completed': '90%'}))
150
151
152 class TestProjectFilters(unittest.TestCase):
153 """Test filters used to decide whether to push/pull a translation or not."""
154
155 def setUp(self):
156 super(TestProjectFilters, self).setUp()
157 self.p = Project(init=False)
158 self.p.minimum_perc = None
159 self.p.resource = "resource"
160 self.stats = {
161 'en': {
162 'completed': '100%', 'last_update': '2011-11-01 15:00:00',
163 }, 'el': {
164 'completed': '60%', 'last_update': '2011-11-01 15:00:00',
165 }, 'pt': {
166 'completed': '70%', 'last_update': '2011-11-01 15:00:00',
167 },
168 }
169 self.langs = self.stats.keys()
170
171 def test_add_translation(self):
172 """Test filters for adding translations.
173
174 We do not test here for minimum percentages.
175 """
176 with patch.object(self.p, "get_resource_option") as mock:
177 mock.return_value = None
178 should_add = self.p._should_add_translation
179 for force in [True, False]:
180 for lang in self.langs:
181 self.assertTrue(should_add(lang, self.stats, force))
182
183 # unknown language
184 self.assertFalse(should_add('es', self.stats))
185
186 def test_update_translation(self):
187 """Test filters for updating a translation.
188
189 We do not test here for minimum percentages.
190 """
191 with patch.object(self.p, "get_resource_option") as mock:
192 mock.return_value = None
193
194 should_update = self.p._should_update_translation
195 force = True
196 for lang in self.langs:
197 self.assertTrue(should_update(lang, self.stats, 'foo', force))
198
199 force = False # reminder
200 local_file = 'foo'
201
202 # unknown language
203 self.assertFalse(should_update('es', self.stats, local_file))
204
205 # no local file
206 with patch.object(self.p, "_get_time_of_local_file") as time_mock:
207 time_mock.return_value = None
208 with patch.object(self.p, "get_full_path") as path_mock:
209 path_mock.return_value = "foo"
210 for lang in self.langs:
211 self.assertTrue(
212 should_update(lang, self.stats, local_file)
213 )
214
215 # older local files
216 local_times = [self.p._generate_timestamp('2011-11-01 14:00:59')]
217 results = itertools.cycle(local_times)
218 def side_effect(*args):
219 return results.next()
220
221 with patch.object(self.p, "_get_time_of_local_file") as time_mock:
222 time_mock.side_effect = side_effect
223 with patch.object(self.p, "get_full_path") as path_mock:
224 path_mock.return_value = "foo"
225 for lang in self.langs:
226 self.assertTrue(
227 should_update(lang, self.stats, local_file)
228 )
229
230 # newer local files
231 local_times = [self.p._generate_timestamp('2011-11-01 15:01:59')]
232 results = itertools.cycle(local_times)
233 def side_effect(*args):
234 return results.next()
235
236 with patch.object(self.p, "_get_time_of_local_file") as time_mock:
237 time_mock.side_effect = side_effect
238 with patch.object(self.p, "get_full_path") as path_mock:
239 path_mock.return_value = "foo"
240 for lang in self.langs:
241 self.assertFalse(
242 should_update(lang, self.stats, local_file)
243 )
244
245 def test_push_translation(self):
246 """Test filters for pushing a translation file."""
247 with patch.object(self.p, "get_resource_option") as mock:
248 mock.return_value = None
249
250 local_file = 'foo'
251 should_push = self.p._should_push_translation
252 force = True
253 for lang in self.langs:
254 self.assertTrue(should_push(lang, self.stats, local_file, force))
255
256 force = False # reminder
257
258 # unknown language
259 self.assertTrue(should_push('es', self.stats, local_file))
260
261 # older local files
262 local_times = [self.p._generate_timestamp('2011-11-01 14:00:59')]
263 results = itertools.cycle(local_times)
264 def side_effect(*args):
265 return results.next()
266
267 with patch.object(self.p, "_get_time_of_local_file") as time_mock:
268 time_mock.side_effect = side_effect
269 with patch.object(self.p, "get_full_path") as path_mock:
270 path_mock.return_value = "foo"
271 for lang in self.langs:
272 self.assertFalse(
273 should_push(lang, self.stats, local_file)
274 )
275
276 # newer local files
277 local_times = [self.p._generate_timestamp('2011-11-01 15:01:59')]
278 results = itertools.cycle(local_times)
279 def side_effect(*args):
280 return results.next()
281
282 with patch.object(self.p, "_get_time_of_local_file") as time_mock:
283 time_mock.side_effect = side_effect
284 with patch.object(self.p, "get_full_path") as path_mock:
285 path_mock.return_value = "foo"
286 for lang in self.langs:
287 self.assertTrue(
288 should_push(lang, self.stats, local_file)
289 )
290
291
292 class TestProjectPull(unittest.TestCase):
293 """Test bits & pieces of the pull method."""
294
295 def setUp(self):
296 super(TestProjectPull, self).setUp()
297 self.p = Project(init=False)
298 self.p.minimum_perc = None
299 self.p.resource = "resource"
300 self.p.host = 'foo'
301 self.p.project_slug = 'foo'
302 self.p.resource_slug = 'foo'
303 self.stats = {
304 'en': {
305 'completed': '100%', 'last_update': '2011-11-01 15:00:00',
306 }, 'el': {
307 'completed': '60%', 'last_update': '2011-11-01 15:00:00',
308 }, 'pt': {
309 'completed': '70%', 'last_update': '2011-11-01 15:00:00',
310 },
311 }
312 self.langs = self.stats.keys()
313 self.files = dict(zip(self.langs, itertools.repeat(None)))
314 self.details = {'available_languages': []}
315 for lang in self.langs:
316 self.details['available_languages'].append({'code': lang})
317 self.slang = 'en'
318 self.lang_map = Flipdict()
319
320 def test_new_translations(self):
321 """Test finding new transaltions to add."""
322 with patch.object(self.p, 'do_url_request') as resource_mock:
323 resource_mock.return_value = json.dumps(self.details)
324 files_keys = self.langs
325 new_trans = self.p._new_translations_to_add
326 for force in [True, False]:
327 res = new_trans(
328 self.files, self.slang, self.lang_map, self.stats, force
329 )
330 self.assertEquals(res, set([]))
331
332 with patch.object(self.p, '_should_add_translation') as filter_mock:
333 filter_mock.return_value = True
334 for force in [True, False]:
335 res = new_trans(
336 {'el': None}, self.slang, self.lang_map, self.stats, force
337 )
338 self.assertEquals(res, set(['pt']))
339 for force in [True, False]:
340 res = new_trans(
341 {}, self.slang, self.lang_map, self.stats, force
342 )
343 self.assertEquals(res, set(['el', 'pt']))
344
345 files = {}
346 files['pt_PT'] = None
347 lang_map = {'pt': 'pt_PT'}
348 for force in [True, False]:
349 res = new_trans(
350 files, self.slang, lang_map, self.stats, force
351 )
352 self.assertEquals(res, set(['el']))
353
354 def test_languages_to_pull_empty_initial_list(self):
355 """Test determining the languages to pull, when the initial
356 list is empty.
357 """
358 languages = []
359 force = False
360
361 res = self.p._languages_to_pull(
362 languages, self.files, self.lang_map, self.stats, force
363 )
364 existing = res[0]
365 new = res[1]
366 self.assertEquals(existing, set(['el', 'en', 'pt']))
367 self.assertFalse(new)
368
369 del self.files['el']
370 self.files['el-gr'] = None
371 self.lang_map['el'] = 'el-gr'
372 res = self.p._languages_to_pull(
373 languages, self.files, self.lang_map, self.stats, force
374 )
375 existing = res[0]
376 new = res[1]
377 self.assertEquals(existing, set(['el', 'en', 'pt']))
378 self.assertFalse(new)
379
380 def test_languages_to_pull_with_initial_list(self):
381 """Test determining the languages to pull, then there is a
382 language selection from the user.
383 """
384 languages = ['el', 'en']
385 self.lang_map['el'] = 'el-gr'
386 del self.files['el']
387 self.files['el-gr'] = None
388 force = False
389
390 with patch.object(self.p, '_should_add_translation') as mock:
391 mock.return_value = True
392 res = self.p._languages_to_pull(
393 languages, self.files, self.lang_map, self.stats, force
394 )
395 existing = res[0]
396 new = res[1]
397 self.assertEquals(existing, set(['en', 'el-gr', ]))
398 self.assertFalse(new)
399
400 mock.return_value = False
401 res = self.p._languages_to_pull(
402 languages, self.files, self.lang_map, self.stats, force
403 )
404 existing = res[0]
405 new = res[1]
406 self.assertEquals(existing, set(['en', 'el-gr', ]))
407 self.assertFalse(new)
408
409 del self.files['el-gr']
410 mock.return_value = True
411 res = self.p._languages_to_pull(
412 languages, self.files, self.lang_map, self.stats, force
413 )
414 existing = res[0]
415 new = res[1]
416 self.assertEquals(existing, set(['en', ]))
417 self.assertEquals(new, set(['el', ]))
418
419 mock.return_value = False
420 res = self.p._languages_to_pull(
421 languages, self.files, self.lang_map, self.stats, force
422 )
423 existing = res[0]
424 new = res[1]
425 self.assertEquals(existing, set(['en', ]))
426 self.assertEquals(new, set([]))
427
428 def test_in_combination_with_force_option(self):
429 """Test the minumum-perc option along with -f."""
430 with patch.object(self.p, 'get_resource_option') as mock:
431 mock.return_value = 70
432
433 res = self.p._should_download('de', self.stats, None, False)
434 self.assertEquals(res, False)
435 res = self.p._should_download('el', self.stats, None, False)
436 self.assertEquals(res, False)
437 res = self.p._should_download('el', self.stats, None, True)
438 self.assertEquals(res, False)
439 res = self.p._should_download('en', self.stats, None, False)
440 self.assertEquals(res, True)
441 res = self.p._should_download('en', self.stats, None, True)
442 self.assertEquals(res, True)
443
444 with patch.object(self.p, '_remote_is_newer') as local_file_mock:
445 local_file_mock = False
446 res = self.p._should_download('pt', self.stats, None, False)
447 self.assertEquals(res, True)
448 res = self.p._should_download('pt', self.stats, None, True)
449 self.assertEquals(res, True)
450
451
452 class TestFormats(unittest.TestCase):
453 """Tests for the supported formats."""
454
455 def setUp(self):
456 self.p = Project(init=False)
457
458 def test_extensions(self):
459 """Test returning the correct extension for a format."""
460 sample_formats = {
461 'PO': {'file-extensions': '.po, .pot'},
462 'QT': {'file-extensions': '.ts'},
463 }
464 extensions = ['.po', '.ts', '', ]
465 with patch.object(self.p, "do_url_request") as mock:
466 mock.return_value = json.dumps(sample_formats)
467 for (type_, ext) in zip(['PO', 'QT', 'NONE', ], extensions):
468 extension = self.p._extension_for(type_)
469 self.assertEquals(extension, ext)
470
471
472 class TestOptions(unittest.TestCase):
473 """Test the methods related to parsing the configuration file."""
474
475 def setUp(self):
476 self.p = Project(init=False)
477
478 def test_get_option(self):
479 """Test _get_option method."""
480 with contextlib.nested(
481 patch.object(self.p, 'get_resource_option'),
482 patch.object(self.p, 'config', create=True)
483 ) as (rmock, cmock):
484 rmock.return_value = 'resource'
485 cmock.has_option.return_value = 'main'
486 cmock.get.return_value = 'main'
487 self.assertEqual(self.p._get_option(None, None), 'resource')
488 rmock.return_value = None
489 cmock.has_option.return_value = 'main'
490 cmock.get.return_value = 'main'
491 self.assertEqual(self.p._get_option(None, None), 'main')
492 cmock.has_option.return_value = None
493 self.assertIs(self.p._get_option(None, None), None)
494
495
496 class TestConfigurationOptions(unittest.TestCase):
497 """Test the various configuration options."""
498
499 def test_i18n_type(self):
500 p = Project(init=False)
501 type_string = 'type'
502 i18n_type = 'PO'
503 with patch.object(p, 'config', create=True) as config_mock:
504 p.set_i18n_type([], i18n_type)
505 calls = config_mock.method_calls
506 self.assertEquals('set', calls[0][0])
507 self.assertEquals('main', calls[0][1][0])
508 p.set_i18n_type(['transifex.txo'], 'PO')
509 calls = config_mock.method_calls
510 self.assertEquals('set', calls[0][0])
511 p.set_i18n_type(['transifex.txo', 'transifex.txn'], 'PO')
512 calls = config_mock.method_calls
513 self.assertEquals('set', calls[0][0])
514 self.assertEquals('set', calls[1][0])
515
516
517 class TestStats(unittest.TestCase):
518 """Test the access to the stats objects."""
519
520 def setUp(self):
521 self.stats = Mock()
522 self.stats.__getitem__ = Mock()
523 self.stats.__getitem__.return_value = '12%'
524
525 def test_field_used_per_mode(self):
526 """Test the fields used for each mode."""
527 Project._extract_completed(self.stats, 'translate')
528 self.stats.__getitem__.assert_called_with('completed')
529 Project._extract_completed(self.stats, 'reviewed')
530 self.stats.__getitem__.assert_called_with('reviewed_percentage')
531