chiark / gitweb /
reusable method for checking if a value is a resId or not
[fdroidserver.git] / tests / update.TestCase
1 #!/usr/bin/env python3
2
3 # http://www.drdobbs.com/testing/unit-testing-with-python/240165163
4
5 import git
6 import inspect
7 import logging
8 import optparse
9 import os
10 import shutil
11 import subprocess
12 import sys
13 import tempfile
14 import unittest
15 import yaml
16 from binascii import unhexlify
17 from distutils.version import LooseVersion
18
19 localmodule = os.path.realpath(
20     os.path.join(os.path.dirname(inspect.getfile(inspect.currentframe())), '..'))
21 print('localmodule: ' + localmodule)
22 if localmodule not in sys.path:
23     sys.path.insert(0, localmodule)
24
25 import fdroidserver.common
26 import fdroidserver.exception
27 import fdroidserver.metadata
28 import fdroidserver.update
29 from fdroidserver.common import FDroidPopen
30
31
32 class UpdateTest(unittest.TestCase):
33     '''fdroid update'''
34
35     def setUp(self):
36         logging.basicConfig(level=logging.INFO)
37         self.basedir = os.path.join(localmodule, 'tests')
38         self.tmpdir = os.path.abspath(os.path.join(self.basedir, '..', '.testfiles'))
39         if not os.path.exists(self.tmpdir):
40             os.makedirs(self.tmpdir)
41         os.chdir(self.basedir)
42
43     def testInsertStoreMetadata(self):
44         config = dict()
45         fdroidserver.common.fill_config_defaults(config)
46         config['accepted_formats'] = ('txt', 'yml')
47         fdroidserver.update.config = config
48         fdroidserver.update.options = fdroidserver.common.options
49         os.chdir(os.path.join(localmodule, 'tests'))
50
51         shutil.rmtree(os.path.join('repo', 'info.guardianproject.urzip'), ignore_errors=True)
52
53         shutil.rmtree(os.path.join('build', 'com.nextcloud.client'), ignore_errors=True)
54         shutil.copytree(os.path.join('source-files', 'com.nextcloud.client'),
55                         os.path.join('build', 'com.nextcloud.client'))
56
57         shutil.rmtree(os.path.join('build', 'com.nextcloud.client.dev'), ignore_errors=True)
58         shutil.copytree(os.path.join('source-files', 'com.nextcloud.client.dev'),
59                         os.path.join('build', 'com.nextcloud.client.dev'))
60
61         shutil.rmtree(os.path.join('build', 'eu.siacs.conversations'), ignore_errors=True)
62         shutil.copytree(os.path.join('source-files', 'eu.siacs.conversations'),
63                         os.path.join('build', 'eu.siacs.conversations'))
64
65         apps = dict()
66         for packageName in ('info.guardianproject.urzip', 'org.videolan.vlc', 'obb.mainpatch.current',
67                             'com.nextcloud.client', 'com.nextcloud.client.dev',
68                             'eu.siacs.conversations'):
69             apps[packageName] = fdroidserver.metadata.App()
70             apps[packageName]['id'] = packageName
71             apps[packageName]['CurrentVersionCode'] = 0xcafebeef
72
73         apps['info.guardianproject.urzip']['CurrentVersionCode'] = 100
74
75         buildnextcloudclient = fdroidserver.metadata.Build()
76         buildnextcloudclient.gradle = ['generic']
77         apps['com.nextcloud.client']['builds'] = [buildnextcloudclient]
78
79         buildnextclouddevclient = fdroidserver.metadata.Build()
80         buildnextclouddevclient.gradle = ['versionDev']
81         apps['com.nextcloud.client.dev']['builds'] = [buildnextclouddevclient]
82
83         build_conversations = fdroidserver.metadata.Build()
84         build_conversations.gradle = ['free']
85         apps['eu.siacs.conversations']['builds'] = [build_conversations]
86
87         fdroidserver.update.insert_localized_app_metadata(apps)
88
89         appdir = os.path.join('repo', 'info.guardianproject.urzip', 'en-US')
90         self.assertTrue(os.path.isfile(os.path.join(appdir, 'icon.png')))
91         self.assertTrue(os.path.isfile(os.path.join(appdir, 'featureGraphic.png')))
92
93         self.assertEqual(6, len(apps))
94         for packageName, app in apps.items():
95             self.assertTrue('localized' in app)
96             self.assertTrue('en-US' in app['localized'])
97             self.assertEqual(1, len(app['localized']))
98             if packageName == 'info.guardianproject.urzip':
99                 self.assertEqual(7, len(app['localized']['en-US']))
100                 self.assertEqual('full description\n', app['localized']['en-US']['description'])
101                 self.assertEqual('title\n', app['localized']['en-US']['name'])
102                 self.assertEqual('short description\n', app['localized']['en-US']['summary'])
103                 self.assertEqual('video\n', app['localized']['en-US']['video'])
104                 self.assertEqual('icon.png', app['localized']['en-US']['icon'])
105                 self.assertEqual('featureGraphic.png', app['localized']['en-US']['featureGraphic'])
106                 self.assertEqual('100\n', app['localized']['en-US']['whatsNew'])
107             elif packageName == 'org.videolan.vlc':
108                 self.assertEqual('icon.png', app['localized']['en-US']['icon'])
109                 self.assertEqual(9, len(app['localized']['en-US']['phoneScreenshots']))
110                 self.assertEqual(15, len(app['localized']['en-US']['sevenInchScreenshots']))
111             elif packageName == 'obb.mainpatch.current':
112                 self.assertEqual('icon.png', app['localized']['en-US']['icon'])
113                 self.assertEqual('featureGraphic.png', app['localized']['en-US']['featureGraphic'])
114                 self.assertEqual(1, len(app['localized']['en-US']['phoneScreenshots']))
115                 self.assertEqual(1, len(app['localized']['en-US']['sevenInchScreenshots']))
116             elif packageName == 'com.nextcloud.client':
117                 self.assertEqual('Nextcloud', app['localized']['en-US']['name'])
118                 self.assertEqual(1073, len(app['localized']['en-US']['description']))
119                 self.assertEqual(78, len(app['localized']['en-US']['summary']))
120             elif packageName == 'com.nextcloud.client.dev':
121                 self.assertEqual('Nextcloud Dev', app['localized']['en-US']['name'])
122                 self.assertEqual(586, len(app['localized']['en-US']['description']))
123                 self.assertEqual(79, len(app['localized']['en-US']['summary']))
124             elif packageName == 'eu.siacs.conversations':
125                 self.assertEqual('Conversations', app['localized']['en-US']['name'])
126
127     def test_insert_triple_t_metadata(self):
128         importer = os.path.join(self.basedir, 'tmp', 'importer')
129         packageName = 'org.fdroid.ci.test.app'
130         if not os.path.isdir(importer):
131             logging.warning('skipping test_insert_triple_t_metadata, import.TestCase must run first!')
132             return
133         tmptestsdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name,
134                                        dir=self.tmpdir)
135         packageDir = os.path.join(tmptestsdir, 'build', packageName)
136         shutil.copytree(importer, packageDir)
137
138         # always use the same commit so these tests work when ci-test-app.git is updated
139         repo = git.Repo(packageDir)
140         for remote in repo.remotes:
141             remote.fetch()
142         repo.git.reset('--hard', 'b9e5d1a0d8d6fc31d4674b2f0514fef10762ed4f')
143         repo.git.clean('-fdx')
144
145         os.mkdir(os.path.join(tmptestsdir, 'metadata'))
146         metadata = dict()
147         metadata['Description'] = 'This is just a test app'
148         with open(os.path.join(tmptestsdir, 'metadata', packageName + '.yml'), 'w') as fp:
149             yaml.dump(metadata, fp)
150
151         config = dict()
152         fdroidserver.common.fill_config_defaults(config)
153         config['accepted_formats'] = ('yml')
154         fdroidserver.common.config = config
155         fdroidserver.update.config = config
156         fdroidserver.update.options = fdroidserver.common.options
157         os.chdir(tmptestsdir)
158
159         apps = fdroidserver.metadata.read_metadata(xref=True)
160         fdroidserver.update.copy_triple_t_store_metadata(apps)
161
162         # TODO ideally, this would compare the whole dict like in metadata.TestCase's test_read_metadata()
163         correctlocales = [
164             'ar', 'ast_ES', 'az', 'ca', 'ca_ES', 'cs-CZ', 'cs_CZ', 'da',
165             'da-DK', 'de', 'de-DE', 'el', 'en-US', 'es', 'es-ES', 'es_ES', 'et',
166             'fi', 'fr', 'fr-FR', 'he_IL', 'hi-IN', 'hi_IN', 'hu', 'id', 'it',
167             'it-IT', 'it_IT', 'iw-IL', 'ja', 'ja-JP', 'kn_IN', 'ko', 'ko-KR',
168             'ko_KR', 'lt', 'nb', 'nb_NO', 'nl', 'nl-NL', 'no', 'pl', 'pl-PL',
169             'pl_PL', 'pt', 'pt-BR', 'pt-PT', 'pt_BR', 'ro', 'ro_RO', 'ru-RU',
170             'ru_RU', 'sv-SE', 'sv_SE', 'te', 'tr', 'tr-TR', 'uk', 'uk_UA', 'vi',
171             'vi_VN', 'zh-CN', 'zh_CN', 'zh_TW',
172         ]
173         locales = sorted(list(apps['org.fdroid.ci.test.app']['localized'].keys()))
174         self.assertEqual(correctlocales, locales)
175
176     def javagetsig(self, apkfile):
177         getsig_dir = os.path.join(os.path.dirname(__file__), 'getsig')
178         if not os.path.exists(getsig_dir + "/getsig.class"):
179             logging.critical("getsig.class not found. To fix: cd '%s' && ./make.sh" % getsig_dir)
180             sys.exit(1)
181         # FDroidPopen needs some config to work
182         config = dict()
183         fdroidserver.common.fill_config_defaults(config)
184         fdroidserver.common.config = config
185         p = FDroidPopen(['java', '-cp', os.path.join(os.path.dirname(__file__), 'getsig'),
186                          'getsig', os.path.join(os.getcwd(), apkfile)])
187         sig = None
188         for line in p.output.splitlines():
189             if line.startswith('Result:'):
190                 sig = line[7:].strip()
191                 break
192         if p.returncode == 0:
193             return sig
194         else:
195             return None
196
197     def testGoodGetsig(self):
198         # config needed to use jarsigner and keytool
199         config = dict()
200         fdroidserver.common.fill_config_defaults(config)
201         fdroidserver.update.config = config
202         apkfile = os.path.join(os.path.dirname(__file__), 'urzip.apk')
203         sig = self.javagetsig(apkfile)
204         self.assertIsNotNone(sig, "sig is None")
205         pysig = fdroidserver.update.getsig(apkfile)
206         self.assertIsNotNone(pysig, "pysig is None")
207         self.assertEqual(sig, fdroidserver.update.getsig(apkfile),
208                          "python sig not equal to java sig!")
209         self.assertEqual(len(sig), len(pysig),
210                          "the length of the two sigs are different!")
211         try:
212             self.assertEqual(unhexlify(sig), unhexlify(pysig),
213                              "the length of the two sigs are different!")
214         except TypeError as e:
215             print(e)
216             self.assertTrue(False, 'TypeError!')
217
218     def testBadGetsig(self):
219         """getsig() should still be able to fetch the fingerprint of bad signatures"""
220         # config needed to use jarsigner and keytool
221         config = dict()
222         fdroidserver.common.fill_config_defaults(config)
223         fdroidserver.update.config = config
224
225         apkfile = os.path.join(os.path.dirname(__file__), 'urzip-badsig.apk')
226         sig = fdroidserver.update.getsig(apkfile)
227         self.assertEqual(sig, 'e0ecb5fc2d63088e4a07ae410a127722',
228                          "python sig should be: " + str(sig))
229
230         apkfile = os.path.join(os.path.dirname(__file__), 'urzip-badcert.apk')
231         sig = fdroidserver.update.getsig(apkfile)
232         self.assertEqual(sig, 'e0ecb5fc2d63088e4a07ae410a127722',
233                          "python sig should be: " + str(sig))
234
235     def testScanApksAndObbs(self):
236         os.chdir(os.path.join(localmodule, 'tests'))
237         if os.path.basename(os.getcwd()) != 'tests':
238             raise Exception('This test must be run in the "tests/" subdir')
239
240         config = dict()
241         fdroidserver.common.fill_config_defaults(config)
242         config['ndk_paths'] = dict()
243         config['accepted_formats'] = ['json', 'txt', 'yml']
244         fdroidserver.common.config = config
245         fdroidserver.update.config = config
246
247         fdroidserver.update.options = type('', (), {})()
248         fdroidserver.update.options.clean = True
249         fdroidserver.update.options.delete_unknown = True
250         fdroidserver.update.options.rename_apks = False
251         fdroidserver.update.options.allow_disabled_algorithms = False
252
253         apps = fdroidserver.metadata.read_metadata(xref=True)
254         knownapks = fdroidserver.common.KnownApks()
255         apks, cachechanged = fdroidserver.update.process_apks({}, 'repo', knownapks, False)
256         self.assertEqual(len(apks), 14)
257         apk = apks[0]
258         self.assertEqual(apk['packageName'], 'com.politedroid')
259         self.assertEqual(apk['versionCode'], 3)
260         self.assertEqual(apk['minSdkVersion'], '3')
261         self.assertEqual(apk['targetSdkVersion'], '3')
262         self.assertFalse('maxSdkVersion' in apk)
263         apk = apks[6]
264         self.assertEqual(apk['packageName'], 'obb.main.oldversion')
265         self.assertEqual(apk['versionCode'], 1444412523)
266         self.assertEqual(apk['minSdkVersion'], '4')
267         self.assertEqual(apk['targetSdkVersion'], '18')
268         self.assertFalse('maxSdkVersion' in apk)
269
270         fdroidserver.update.insert_obbs('repo', apps, apks)
271         for apk in apks:
272             if apk['packageName'] == 'obb.mainpatch.current':
273                 self.assertEqual(apk.get('obbMainFile'), 'main.1619.obb.mainpatch.current.obb')
274                 self.assertEqual(apk.get('obbPatchFile'), 'patch.1619.obb.mainpatch.current.obb')
275             elif apk['packageName'] == 'obb.main.oldversion':
276                 self.assertEqual(apk.get('obbMainFile'), 'main.1434483388.obb.main.oldversion.obb')
277                 self.assertIsNone(apk.get('obbPatchFile'))
278             elif apk['packageName'] == 'obb.main.twoversions':
279                 self.assertIsNone(apk.get('obbPatchFile'))
280                 if apk['versionCode'] == 1101613:
281                     self.assertEqual(apk.get('obbMainFile'), 'main.1101613.obb.main.twoversions.obb')
282                 elif apk['versionCode'] == 1101615:
283                     self.assertEqual(apk.get('obbMainFile'), 'main.1101615.obb.main.twoversions.obb')
284                 elif apk['versionCode'] == 1101617:
285                     self.assertEqual(apk.get('obbMainFile'), 'main.1101615.obb.main.twoversions.obb')
286                 else:
287                     self.assertTrue(False)
288             elif apk['packageName'] == 'info.guardianproject.urzip':
289                 self.assertIsNone(apk.get('obbMainFile'))
290                 self.assertIsNone(apk.get('obbPatchFile'))
291
292     def test_scan_apk(self):
293         config = dict()
294         fdroidserver.common.fill_config_defaults(config)
295         fdroidserver.common.config = config
296         fdroidserver.update.config = config
297         os.chdir(os.path.join(localmodule, 'tests'))
298         if os.path.basename(os.getcwd()) != 'tests':
299             raise Exception('This test must be run in the "tests/" subdir')
300
301         apk_info = fdroidserver.update.scan_apk('repo/souch.smsbypass_9.apk')
302         self.assertIsNone(apk_info.get('maxSdkVersion'))
303         self.assertEqual(apk_info.get('versionName'), '0.9')
304
305         apk_info = fdroidserver.update.scan_apk('repo/duplicate.permisssions_9999999.apk')
306         self.assertIsNone(apk_info.get('versionName'))
307         self.assertEqual(apk_info['icons_src'], {'160': 'res/drawable/ic_launcher.png',
308                                                  '-1': 'res/drawable/ic_launcher.png'})
309
310         apk_info = fdroidserver.update.scan_apk('org.dyndns.fules.ck_20.apk')
311         self.assertEqual(apk_info['icons_src'], {'240': 'res/drawable-hdpi-v4/icon_launcher.png',
312                                                  '120': 'res/drawable-ldpi-v4/icon_launcher.png',
313                                                  '160': 'res/drawable-mdpi-v4/icon_launcher.png',
314                                                  '-1': 'res/drawable-mdpi-v4/icon_launcher.png'})
315         self.assertEqual(apk_info['icons'], {})
316         self.assertEqual(apk_info['features'], [])
317         self.assertEqual(apk_info['antiFeatures'], set())
318         self.assertEqual(apk_info['versionName'], 'v1.6pre2')
319         self.assertEqual(apk_info['hash'],
320                          '897486e1f857c6c0ee32ccbad0e1b8cd82f6d0e65a44a23f13f852d2b63a18c8')
321         self.assertEqual(apk_info['packageName'], 'org.dyndns.fules.ck')
322         self.assertEqual(apk_info['versionCode'], 20)
323         self.assertEqual(apk_info['size'], 132453)
324         self.assertEqual(apk_info['nativecode'],
325                          ['arm64-v8a', 'armeabi', 'armeabi-v7a', 'mips', 'mips64', 'x86', 'x86_64'])
326         self.assertEqual(apk_info['minSdkVersion'], '7')
327         self.assertEqual(apk_info['sig'], '9bf7a6a67f95688daec75eab4b1436ac')
328         self.assertEqual(apk_info['hashType'], 'sha256')
329         self.assertEqual(apk_info['targetSdkVersion'], '8')
330
331         apk_info = fdroidserver.update.scan_apk('org.bitbucket.tickytacky.mirrormirror_4.apk')
332         self.assertEqual(apk_info.get('versionName'), '1.0.3')
333         self.assertEqual(apk_info['icons_src'], {'160': 'res/drawable-mdpi/mirror.png',
334                                                  '-1': 'res/drawable-mdpi/mirror.png'})
335
336         apk_info = fdroidserver.update.scan_apk('repo/info.zwanenburg.caffeinetile_4.apk')
337         self.assertEqual(apk_info.get('versionName'), '1.3')
338         self.assertEqual(apk_info['icons_src'], {'160': 'res/drawable/ic_coffee_on.xml',
339                                                  '-1': 'res/drawable/ic_coffee_on.xml'})
340
341         apk_info = fdroidserver.update.scan_apk('repo/com.politedroid_6.apk')
342         self.assertEqual(apk_info.get('versionName'), '1.5')
343         self.assertEqual(apk_info['icons_src'], {'120': 'res/drawable-ldpi-v4/icon.png',
344                                                  '160': 'res/drawable-mdpi-v4/icon.png',
345                                                  '240': 'res/drawable-hdpi-v4/icon.png',
346                                                  '320': 'res/drawable-xhdpi-v4/icon.png',
347                                                  '-1': 'res/drawable-mdpi-v4/icon.png'})
348
349         apk_info = fdroidserver.update.scan_apk('SpeedoMeterApp.main_1.apk')
350         self.assertEqual(apk_info.get('versionName'), '1.0')
351         self.assertEqual(apk_info['icons_src'], {})
352
353     def test_scan_apk_no_sig(self):
354         config = dict()
355         fdroidserver.common.fill_config_defaults(config)
356         fdroidserver.update.config = config
357         os.chdir(os.path.join(localmodule, 'tests'))
358         if os.path.basename(os.getcwd()) != 'tests':
359             raise Exception('This test must be run in the "tests/" subdir')
360
361         with self.assertRaises(fdroidserver.exception.BuildException):
362             fdroidserver.update.scan_apk('urzip-release-unsigned.apk')
363
364     def test_process_apk(self):
365
366         def _build_yaml_representer(dumper, data):
367             '''Creates a YAML representation of a Build instance'''
368             return dumper.represent_dict(data)
369
370         config = dict()
371         fdroidserver.common.fill_config_defaults(config)
372         fdroidserver.update.config = config
373         os.chdir(os.path.join(localmodule, 'tests'))
374         if os.path.basename(os.getcwd()) != 'tests':
375             raise Exception('This test must be run in the "tests/" subdir')
376
377         config['ndk_paths'] = dict()
378         config['accepted_formats'] = ['json', 'txt', 'yml']
379         fdroidserver.common.config = config
380         fdroidserver.update.config = config
381
382         fdroidserver.update.options = type('', (), {})()
383         fdroidserver.update.options.clean = True
384         fdroidserver.update.options.rename_apks = False
385         fdroidserver.update.options.delete_unknown = True
386         fdroidserver.update.options.allow_disabled_algorithms = False
387
388         for icon_dir in fdroidserver.update.get_all_icon_dirs('repo'):
389             if not os.path.exists(icon_dir):
390                 os.makedirs(icon_dir)
391
392         knownapks = fdroidserver.common.KnownApks()
393         apkList = ['../urzip.apk', '../org.dyndns.fules.ck_20.apk']
394
395         for apkName in apkList:
396             _, apk, cachechanged = fdroidserver.update.process_apk({}, apkName, 'repo', knownapks,
397                                                                    False)
398             # Don't care about the date added to the repo and relative apkName
399             del apk['added']
400             del apk['apkName']
401             # avoid AAPT application name bug
402             del apk['name']
403
404             # ensure that icons have been extracted properly
405             if apkName == '../urzip.apk':
406                 self.assertEqual(apk['icon'], 'info.guardianproject.urzip.100.png')
407             if apkName == '../org.dyndns.fules.ck_20.apk':
408                 self.assertEqual(apk['icon'], 'org.dyndns.fules.ck.20.png')
409             for density in fdroidserver.update.screen_densities:
410                 icon_path = os.path.join(fdroidserver.update.get_icon_dir('repo', density),
411                                          apk['icon'])
412                 self.assertTrue(os.path.isfile(icon_path))
413                 self.assertTrue(os.path.getsize(icon_path) > 1)
414
415             savepath = os.path.join('metadata', 'apk', apk['packageName'] + '.yaml')
416             # Uncomment to save APK metadata
417             # with open(savepath, 'w') as f:
418             #     yaml.add_representer(fdroidserver.metadata.Build, _build_yaml_representer)
419             #     yaml.dump(apk, f, default_flow_style=False)
420
421             with open(savepath, 'r') as f:
422                 frompickle = yaml.load(f)
423             self.maxDiff = None
424             self.assertEqual(apk, frompickle)
425
426     def test_process_apk_signed_by_disabled_algorithms(self):
427         config = dict()
428         fdroidserver.common.fill_config_defaults(config)
429         fdroidserver.update.config = config
430
431         config['ndk_paths'] = dict()
432         config['accepted_formats'] = ['json', 'txt', 'yml']
433         fdroidserver.common.config = config
434         fdroidserver.update.config = config
435
436         fdroidserver.update.options = type('', (), {})()
437         fdroidserver.update.options.clean = True
438         fdroidserver.update.options.verbose = True
439         fdroidserver.update.options.rename_apks = False
440         fdroidserver.update.options.delete_unknown = True
441         fdroidserver.update.options.allow_disabled_algorithms = False
442
443         knownapks = fdroidserver.common.KnownApks()
444
445         tmptestsdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name,
446                                        dir=self.tmpdir)
447         print('tmptestsdir', tmptestsdir)
448         os.chdir(tmptestsdir)
449         os.mkdir('repo')
450         os.mkdir('archive')
451         # setup the repo, create icons dirs, etc.
452         fdroidserver.update.process_apks({}, 'repo', knownapks)
453         fdroidserver.update.process_apks({}, 'archive', knownapks)
454
455         disabledsigs = ['org.bitbucket.tickytacky.mirrormirror_2.apk', ]
456         for apkName in disabledsigs:
457             shutil.copy(os.path.join(self.basedir, apkName),
458                         os.path.join(tmptestsdir, 'repo'))
459
460             skip, apk, cachechanged = fdroidserver.update.process_apk({}, apkName, 'repo',
461                                                                       knownapks,
462                                                                       allow_disabled_algorithms=True,
463                                                                       archive_bad_sig=False)
464             self.assertFalse(skip)
465             self.assertIsNotNone(apk)
466             self.assertTrue(cachechanged)
467             self.assertFalse(os.path.exists(os.path.join('archive', apkName)))
468             self.assertTrue(os.path.exists(os.path.join('repo', apkName)))
469
470             javac = config['jarsigner'].replace('jarsigner', 'javac')
471             v = subprocess.check_output([javac, '-version'], stderr=subprocess.STDOUT)[6:-1].decode('utf-8')
472             if LooseVersion(v) < LooseVersion('1.8.0_132'):
473                 print('SKIPPING: running tests with old Java (' + v + ')')
474                 return
475
476             # this test only works on systems with fully updated Java/jarsigner
477             # that has MD5 listed in jdk.jar.disabledAlgorithms in java.security
478             # https://blogs.oracle.com/java-platform-group/oracle-jre-will-no-longer-trust-md5-signed-code-by-default
479             skip, apk, cachechanged = fdroidserver.update.process_apk({}, apkName, 'repo',
480                                                                       knownapks,
481                                                                       allow_disabled_algorithms=False,
482                                                                       archive_bad_sig=True)
483             self.assertTrue(skip)
484             self.assertIsNone(apk)
485             self.assertFalse(cachechanged)
486             self.assertTrue(os.path.exists(os.path.join('archive', apkName)))
487             self.assertFalse(os.path.exists(os.path.join('repo', apkName)))
488
489             skip, apk, cachechanged = fdroidserver.update.process_apk({}, apkName, 'archive',
490                                                                       knownapks,
491                                                                       allow_disabled_algorithms=False,
492                                                                       archive_bad_sig=False)
493             self.assertFalse(skip)
494             self.assertIsNotNone(apk)
495             self.assertTrue(cachechanged)
496             self.assertTrue(os.path.exists(os.path.join('archive', apkName)))
497             self.assertFalse(os.path.exists(os.path.join('repo', apkName)))
498
499             # ensure that icons have been moved to the archive as well
500             for density in fdroidserver.update.screen_densities:
501                 icon_path = os.path.join(fdroidserver.update.get_icon_dir('archive', density),
502                                          apk['icon'])
503                 self.assertTrue(os.path.isfile(icon_path))
504                 self.assertTrue(os.path.getsize(icon_path) > 1)
505
506         badsigs = ['urzip-badcert.apk', 'urzip-badsig.apk', 'urzip-release-unsigned.apk', ]
507         for apkName in badsigs:
508             shutil.copy(os.path.join(self.basedir, apkName),
509                         os.path.join(tmptestsdir, 'repo'))
510
511             skip, apk, cachechanged = fdroidserver.update.process_apk({}, apkName, 'repo',
512                                                                       knownapks,
513                                                                       allow_disabled_algorithms=False,
514                                                                       archive_bad_sig=False)
515             self.assertTrue(skip)
516             self.assertIsNone(apk)
517             self.assertFalse(cachechanged)
518
519     def test_process_invalid_apk(self):
520         os.chdir(os.path.join(localmodule, 'tests'))
521         if os.path.basename(os.getcwd()) != 'tests':
522             raise Exception('This test must be run in the "tests/" subdir')
523
524         config = dict()
525         fdroidserver.common.fill_config_defaults(config)
526         fdroidserver.common.config = config
527         fdroidserver.update.config = config
528         fdroidserver.update.options.delete_unknown = False
529
530         knownapks = fdroidserver.common.KnownApks()
531         apk = 'fake.ota.update_1234.zip'  # this is not an APK, scanning should fail
532         (skip, apk, cachechanged) = fdroidserver.update.process_apk({}, apk, 'repo', knownapks,
533                                                                     False)
534
535         self.assertTrue(skip)
536         self.assertIsNone(apk)
537         self.assertFalse(cachechanged)
538
539     def test_translate_per_build_anti_features(self):
540         os.chdir(os.path.join(localmodule, 'tests'))
541         if os.path.basename(os.getcwd()) != 'tests':
542             raise Exception('This test must be run in the "tests/" subdir')
543
544         config = dict()
545         fdroidserver.common.fill_config_defaults(config)
546         config['ndk_paths'] = dict()
547         config['accepted_formats'] = ['json', 'txt', 'yml']
548         fdroidserver.common.config = config
549         fdroidserver.update.config = config
550
551         fdroidserver.update.options = type('', (), {})()
552         fdroidserver.update.options.clean = True
553         fdroidserver.update.options.delete_unknown = True
554         fdroidserver.update.options.rename_apks = False
555         fdroidserver.update.options.allow_disabled_algorithms = False
556
557         apps = fdroidserver.metadata.read_metadata(xref=True)
558         knownapks = fdroidserver.common.KnownApks()
559         apks, cachechanged = fdroidserver.update.process_apks({}, 'repo', knownapks, False)
560         fdroidserver.update.translate_per_build_anti_features(apps, apks)
561         self.assertEqual(len(apks), 14)
562         foundtest = False
563         for apk in apks:
564             if apk['packageName'] == 'com.politedroid' and apk['versionCode'] == 3:
565                 antiFeatures = apk.get('antiFeatures')
566                 self.assertTrue('KnownVuln' in antiFeatures)
567                 self.assertEqual(3, len(antiFeatures))
568                 foundtest = True
569         self.assertTrue(foundtest)
570
571     def test_create_metadata_from_template(self):
572         tmptestsdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name,
573                                        dir=self.tmpdir)
574         print('tmptestsdir', tmptestsdir)
575         os.chdir(tmptestsdir)
576         os.mkdir('repo')
577         os.mkdir('metadata')
578         shutil.copy(os.path.join(localmodule, 'tests', 'urzip.apk'), 'repo')
579
580         config = dict()
581         fdroidserver.common.fill_config_defaults(config)
582         config['ndk_paths'] = dict()
583         config['accepted_formats'] = ['json', 'txt', 'yml']
584         fdroidserver.common.config = config
585         fdroidserver.update.config = config
586
587         fdroidserver.update.options = type('', (), {})()
588         fdroidserver.update.options.clean = True
589         fdroidserver.update.options.delete_unknown = False
590         fdroidserver.update.options.rename_apks = False
591         fdroidserver.update.options.allow_disabled_algorithms = False
592
593         knownapks = fdroidserver.common.KnownApks()
594         apks, cachechanged = fdroidserver.update.process_apks({}, 'repo', knownapks, False)
595         self.assertEqual(1, len(apks))
596         apk = apks[0]
597
598         testfile = 'metadata/info.guardianproject.urzip.yml'
599         # create empty 0 byte .yml file, run read_metadata, it should work
600         open(testfile, 'a').close()
601         apps = fdroidserver.metadata.read_metadata(xref=True)
602         self.assertEqual(1, len(apps))
603         os.remove(testfile)
604
605         # test using internal template
606         apps = fdroidserver.metadata.read_metadata(xref=True)
607         self.assertEqual(0, len(apps))
608         fdroidserver.update.create_metadata_from_template(apk)
609         self.assertTrue(os.path.exists(testfile))
610         apps = fdroidserver.metadata.read_metadata(xref=True)
611         self.assertEqual(1, len(apps))
612         for app in apps.values():
613             self.assertEqual('urzip', app['Name'])
614             self.assertEqual(1, len(app['Categories']))
615             break
616
617         # test using external template.yml
618         os.remove(testfile)
619         self.assertFalse(os.path.exists(testfile))
620         shutil.copy(os.path.join(localmodule, 'examples', 'template.yml'), tmptestsdir)
621         fdroidserver.update.create_metadata_from_template(apk)
622         self.assertTrue(os.path.exists(testfile))
623         apps = fdroidserver.metadata.read_metadata(xref=True)
624         self.assertEqual(1, len(apps))
625         for app in apps.values():
626             self.assertEqual('urzip', app['Name'])
627             self.assertEqual(1, len(app['Categories']))
628             self.assertEqual('Internet', app['Categories'][0])
629             break
630         with open(testfile) as fp:
631             data = yaml.load(fp)
632         self.assertEqual('urzip', data['Name'])
633         self.assertEqual('urzip', data['Summary'])
634
635     def test_has_known_vulnerability(self):
636         good = [
637             'org.bitbucket.tickytacky.mirrormirror_1.apk',
638             'org.bitbucket.tickytacky.mirrormirror_2.apk',
639             'org.bitbucket.tickytacky.mirrormirror_3.apk',
640             'org.bitbucket.tickytacky.mirrormirror_4.apk',
641             'org.dyndns.fules.ck_20.apk',
642             'urzip.apk',
643             'urzip-badcert.apk',
644             'urzip-badsig.apk',
645             'urzip-release.apk',
646             'urzip-release-unsigned.apk',
647             'repo/com.politedroid_3.apk',
648             'repo/com.politedroid_4.apk',
649             'repo/com.politedroid_5.apk',
650             'repo/com.politedroid_6.apk',
651             'repo/obb.main.oldversion_1444412523.apk',
652             'repo/obb.mainpatch.current_1619_another-release-key.apk',
653             'repo/obb.mainpatch.current_1619.apk',
654             'repo/obb.main.twoversions_1101613.apk',
655             'repo/obb.main.twoversions_1101615.apk',
656             'repo/obb.main.twoversions_1101617.apk',
657             'repo/urzip-; Рахма́нинов, [rɐxˈmanʲɪnəf] سيرجي_رخمانينوف 谢尔盖·.apk',
658         ]
659         for f in good:
660             self.assertFalse(fdroidserver.update.has_known_vulnerability(f))
661         with self.assertRaises(fdroidserver.exception.FDroidException):
662             fdroidserver.update.has_known_vulnerability('janus.apk')
663
664     def test_get_apk_icon_when_src_is_none(self):
665         config = dict()
666         fdroidserver.common.fill_config_defaults(config)
667         fdroidserver.common.config = config
668         fdroidserver.update.config = config
669
670         # pylint: disable=protected-access
671         icons_src = fdroidserver.update._get_apk_icons_src('urzip-release.apk', None)
672         assert icons_src == {}
673
674
675 if __name__ == "__main__":
676     parser = optparse.OptionParser()
677     parser.add_option("-v", "--verbose", action="store_true", default=False,
678                       help="Spew out even more information than normal")
679     (fdroidserver.common.options, args) = parser.parse_args(['--verbose'])
680
681     newSuite = unittest.TestSuite()
682     newSuite.addTest(unittest.makeSuite(UpdateTest))
683     unittest.main(failfast=False)