chiark / gitweb /
6616669feff431bcbd8e40ab1932522aba950ad9
[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.assertEqual(apk_info['icons_src'], {'160': 'res/drawable/ic_launcher.png',
307                                                  '-1': 'res/drawable/ic_launcher.png'})
308
309         apk_info = fdroidserver.update.scan_apk('org.dyndns.fules.ck_20.apk')
310         self.assertEqual(apk_info['icons_src'], {'240': 'res/drawable-hdpi-v4/icon_launcher.png',
311                                                  '120': 'res/drawable-ldpi-v4/icon_launcher.png',
312                                                  '160': 'res/drawable-mdpi-v4/icon_launcher.png',
313                                                  '-1': 'res/drawable-mdpi-v4/icon_launcher.png'})
314         self.assertEqual(apk_info['icons'], {})
315         self.assertEqual(apk_info['features'], [])
316         self.assertEqual(apk_info['antiFeatures'], set())
317         self.assertEqual(apk_info['versionName'], 'v1.6pre2')
318         self.assertEqual(apk_info['hash'],
319                          '897486e1f857c6c0ee32ccbad0e1b8cd82f6d0e65a44a23f13f852d2b63a18c8')
320         self.assertEqual(apk_info['packageName'], 'org.dyndns.fules.ck')
321         self.assertEqual(apk_info['versionCode'], 20)
322         self.assertEqual(apk_info['size'], 132453)
323         self.assertEqual(apk_info['nativecode'],
324                          ['arm64-v8a', 'armeabi', 'armeabi-v7a', 'mips', 'mips64', 'x86', 'x86_64'])
325         self.assertEqual(apk_info['minSdkVersion'], '7')
326         self.assertEqual(apk_info['sig'], '9bf7a6a67f95688daec75eab4b1436ac')
327         self.assertEqual(apk_info['hashType'], 'sha256')
328         self.assertEqual(apk_info['targetSdkVersion'], '8')
329
330         apk_info = fdroidserver.update.scan_apk('org.bitbucket.tickytacky.mirrormirror_4.apk')
331         self.assertEqual(apk_info['icons_src'], {'160': 'res/drawable-mdpi/mirror.png',
332                                                  '-1': 'res/drawable-mdpi/mirror.png'})
333
334         apk_info = fdroidserver.update.scan_apk('repo/info.zwanenburg.caffeinetile_4.apk')
335         self.assertEqual(apk_info['icons_src'], {'160': 'res/drawable/ic_coffee_on.xml',
336                                                  '-1': 'res/drawable/ic_coffee_on.xml'})
337
338         apk_info = fdroidserver.update.scan_apk('repo/com.politedroid_6.apk')
339         self.assertEqual(apk_info['icons_src'], {'120': 'res/drawable-ldpi-v4/icon.png',
340                                                  '160': 'res/drawable-mdpi-v4/icon.png',
341                                                  '240': 'res/drawable-hdpi-v4/icon.png',
342                                                  '320': 'res/drawable-xhdpi-v4/icon.png',
343                                                  '-1': 'res/drawable-mdpi-v4/icon.png'})
344
345         apk_info = fdroidserver.update.scan_apk('SpeedoMeterApp.main_1.apk')
346         self.assertEqual(apk_info['icons_src'], {})
347
348     def test_scan_apk_no_sig(self):
349         config = dict()
350         fdroidserver.common.fill_config_defaults(config)
351         fdroidserver.update.config = config
352         os.chdir(os.path.join(localmodule, 'tests'))
353         if os.path.basename(os.getcwd()) != 'tests':
354             raise Exception('This test must be run in the "tests/" subdir')
355
356         with self.assertRaises(fdroidserver.exception.BuildException):
357             fdroidserver.update.scan_apk('urzip-release-unsigned.apk')
358
359     def test_process_apk(self):
360
361         def _build_yaml_representer(dumper, data):
362             '''Creates a YAML representation of a Build instance'''
363             return dumper.represent_dict(data)
364
365         config = dict()
366         fdroidserver.common.fill_config_defaults(config)
367         fdroidserver.update.config = config
368         os.chdir(os.path.join(localmodule, 'tests'))
369         if os.path.basename(os.getcwd()) != 'tests':
370             raise Exception('This test must be run in the "tests/" subdir')
371
372         config['ndk_paths'] = dict()
373         config['accepted_formats'] = ['json', 'txt', 'yml']
374         fdroidserver.common.config = config
375         fdroidserver.update.config = config
376
377         fdroidserver.update.options = type('', (), {})()
378         fdroidserver.update.options.clean = True
379         fdroidserver.update.options.rename_apks = False
380         fdroidserver.update.options.delete_unknown = True
381         fdroidserver.update.options.allow_disabled_algorithms = False
382
383         for icon_dir in fdroidserver.update.get_all_icon_dirs('repo'):
384             if not os.path.exists(icon_dir):
385                 os.makedirs(icon_dir)
386
387         knownapks = fdroidserver.common.KnownApks()
388         apkList = ['../urzip.apk', '../org.dyndns.fules.ck_20.apk']
389
390         for apkName in apkList:
391             _, apk, cachechanged = fdroidserver.update.process_apk({}, apkName, 'repo', knownapks,
392                                                                    False)
393             # Don't care about the date added to the repo and relative apkName
394             del apk['added']
395             del apk['apkName']
396             # avoid AAPT application name bug
397             del apk['name']
398
399             # ensure that icons have been extracted properly
400             if apkName == '../urzip.apk':
401                 self.assertEqual(apk['icon'], 'info.guardianproject.urzip.100.png')
402             if apkName == '../org.dyndns.fules.ck_20.apk':
403                 self.assertEqual(apk['icon'], 'org.dyndns.fules.ck.20.png')
404             for density in fdroidserver.update.screen_densities:
405                 icon_path = os.path.join(fdroidserver.update.get_icon_dir('repo', density),
406                                          apk['icon'])
407                 self.assertTrue(os.path.isfile(icon_path))
408                 self.assertTrue(os.path.getsize(icon_path) > 1)
409
410             savepath = os.path.join('metadata', 'apk', apk['packageName'] + '.yaml')
411             # Uncomment to save APK metadata
412             # with open(savepath, 'w') as f:
413             #     yaml.add_representer(fdroidserver.metadata.Build, _build_yaml_representer)
414             #     yaml.dump(apk, f, default_flow_style=False)
415
416             with open(savepath, 'r') as f:
417                 frompickle = yaml.load(f)
418             self.maxDiff = None
419             self.assertEqual(apk, frompickle)
420
421     def test_process_apk_signed_by_disabled_algorithms(self):
422         config = dict()
423         fdroidserver.common.fill_config_defaults(config)
424         fdroidserver.update.config = config
425
426         config['ndk_paths'] = dict()
427         config['accepted_formats'] = ['json', 'txt', 'yml']
428         fdroidserver.common.config = config
429         fdroidserver.update.config = config
430
431         fdroidserver.update.options = type('', (), {})()
432         fdroidserver.update.options.clean = True
433         fdroidserver.update.options.verbose = True
434         fdroidserver.update.options.rename_apks = False
435         fdroidserver.update.options.delete_unknown = True
436         fdroidserver.update.options.allow_disabled_algorithms = False
437
438         knownapks = fdroidserver.common.KnownApks()
439
440         tmptestsdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name,
441                                        dir=self.tmpdir)
442         print('tmptestsdir', tmptestsdir)
443         os.chdir(tmptestsdir)
444         os.mkdir('repo')
445         os.mkdir('archive')
446         # setup the repo, create icons dirs, etc.
447         fdroidserver.update.process_apks({}, 'repo', knownapks)
448         fdroidserver.update.process_apks({}, 'archive', knownapks)
449
450         disabledsigs = ['org.bitbucket.tickytacky.mirrormirror_2.apk', ]
451         for apkName in disabledsigs:
452             shutil.copy(os.path.join(self.basedir, apkName),
453                         os.path.join(tmptestsdir, 'repo'))
454
455             skip, apk, cachechanged = fdroidserver.update.process_apk({}, apkName, 'repo',
456                                                                       knownapks,
457                                                                       allow_disabled_algorithms=True,
458                                                                       archive_bad_sig=False)
459             self.assertFalse(skip)
460             self.assertIsNotNone(apk)
461             self.assertTrue(cachechanged)
462             self.assertFalse(os.path.exists(os.path.join('archive', apkName)))
463             self.assertTrue(os.path.exists(os.path.join('repo', apkName)))
464
465             javac = config['jarsigner'].replace('jarsigner', 'javac')
466             v = subprocess.check_output([javac, '-version'], stderr=subprocess.STDOUT)[6:-1].decode('utf-8')
467             if LooseVersion(v) < LooseVersion('1.8.0_132'):
468                 print('SKIPPING: running tests with old Java (' + v + ')')
469                 return
470
471             # this test only works on systems with fully updated Java/jarsigner
472             # that has MD5 listed in jdk.jar.disabledAlgorithms in java.security
473             # https://blogs.oracle.com/java-platform-group/oracle-jre-will-no-longer-trust-md5-signed-code-by-default
474             skip, apk, cachechanged = fdroidserver.update.process_apk({}, apkName, 'repo',
475                                                                       knownapks,
476                                                                       allow_disabled_algorithms=False,
477                                                                       archive_bad_sig=True)
478             self.assertTrue(skip)
479             self.assertIsNone(apk)
480             self.assertFalse(cachechanged)
481             self.assertTrue(os.path.exists(os.path.join('archive', apkName)))
482             self.assertFalse(os.path.exists(os.path.join('repo', apkName)))
483
484             skip, apk, cachechanged = fdroidserver.update.process_apk({}, apkName, 'archive',
485                                                                       knownapks,
486                                                                       allow_disabled_algorithms=False,
487                                                                       archive_bad_sig=False)
488             self.assertFalse(skip)
489             self.assertIsNotNone(apk)
490             self.assertTrue(cachechanged)
491             self.assertTrue(os.path.exists(os.path.join('archive', apkName)))
492             self.assertFalse(os.path.exists(os.path.join('repo', apkName)))
493
494             # ensure that icons have been moved to the archive as well
495             for density in fdroidserver.update.screen_densities:
496                 icon_path = os.path.join(fdroidserver.update.get_icon_dir('archive', density),
497                                          apk['icon'])
498                 self.assertTrue(os.path.isfile(icon_path))
499                 self.assertTrue(os.path.getsize(icon_path) > 1)
500
501         badsigs = ['urzip-badcert.apk', 'urzip-badsig.apk', 'urzip-release-unsigned.apk', ]
502         for apkName in badsigs:
503             shutil.copy(os.path.join(self.basedir, apkName),
504                         os.path.join(tmptestsdir, 'repo'))
505
506             skip, apk, cachechanged = fdroidserver.update.process_apk({}, apkName, 'repo',
507                                                                       knownapks,
508                                                                       allow_disabled_algorithms=False,
509                                                                       archive_bad_sig=False)
510             self.assertTrue(skip)
511             self.assertIsNone(apk)
512             self.assertFalse(cachechanged)
513
514     def test_process_invalid_apk(self):
515         os.chdir(os.path.join(localmodule, 'tests'))
516         if os.path.basename(os.getcwd()) != 'tests':
517             raise Exception('This test must be run in the "tests/" subdir')
518
519         config = dict()
520         fdroidserver.common.fill_config_defaults(config)
521         fdroidserver.common.config = config
522         fdroidserver.update.config = config
523         fdroidserver.update.options.delete_unknown = False
524
525         knownapks = fdroidserver.common.KnownApks()
526         apk = 'fake.ota.update_1234.zip'  # this is not an APK, scanning should fail
527         (skip, apk, cachechanged) = fdroidserver.update.process_apk({}, apk, 'repo', knownapks,
528                                                                     False)
529
530         self.assertTrue(skip)
531         self.assertIsNone(apk)
532         self.assertFalse(cachechanged)
533
534     def test_translate_per_build_anti_features(self):
535         os.chdir(os.path.join(localmodule, 'tests'))
536         if os.path.basename(os.getcwd()) != 'tests':
537             raise Exception('This test must be run in the "tests/" subdir')
538
539         config = dict()
540         fdroidserver.common.fill_config_defaults(config)
541         config['ndk_paths'] = dict()
542         config['accepted_formats'] = ['json', 'txt', 'yml']
543         fdroidserver.common.config = config
544         fdroidserver.update.config = config
545
546         fdroidserver.update.options = type('', (), {})()
547         fdroidserver.update.options.clean = True
548         fdroidserver.update.options.delete_unknown = True
549         fdroidserver.update.options.rename_apks = False
550         fdroidserver.update.options.allow_disabled_algorithms = False
551
552         apps = fdroidserver.metadata.read_metadata(xref=True)
553         knownapks = fdroidserver.common.KnownApks()
554         apks, cachechanged = fdroidserver.update.process_apks({}, 'repo', knownapks, False)
555         fdroidserver.update.translate_per_build_anti_features(apps, apks)
556         self.assertEqual(len(apks), 14)
557         foundtest = False
558         for apk in apks:
559             if apk['packageName'] == 'com.politedroid' and apk['versionCode'] == 3:
560                 antiFeatures = apk.get('antiFeatures')
561                 self.assertTrue('KnownVuln' in antiFeatures)
562                 self.assertEqual(3, len(antiFeatures))
563                 foundtest = True
564         self.assertTrue(foundtest)
565
566     def test_create_metadata_from_template(self):
567         tmptestsdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name,
568                                        dir=self.tmpdir)
569         print('tmptestsdir', tmptestsdir)
570         os.chdir(tmptestsdir)
571         os.mkdir('repo')
572         os.mkdir('metadata')
573         shutil.copy(os.path.join(localmodule, 'tests', 'urzip.apk'), 'repo')
574
575         config = dict()
576         fdroidserver.common.fill_config_defaults(config)
577         config['ndk_paths'] = dict()
578         config['accepted_formats'] = ['json', 'txt', 'yml']
579         fdroidserver.common.config = config
580         fdroidserver.update.config = config
581
582         fdroidserver.update.options = type('', (), {})()
583         fdroidserver.update.options.clean = True
584         fdroidserver.update.options.delete_unknown = False
585         fdroidserver.update.options.rename_apks = False
586         fdroidserver.update.options.allow_disabled_algorithms = False
587
588         knownapks = fdroidserver.common.KnownApks()
589         apks, cachechanged = fdroidserver.update.process_apks({}, 'repo', knownapks, False)
590         self.assertEqual(1, len(apks))
591         apk = apks[0]
592
593         testfile = 'metadata/info.guardianproject.urzip.yml'
594         # create empty 0 byte .yml file, run read_metadata, it should work
595         open(testfile, 'a').close()
596         apps = fdroidserver.metadata.read_metadata(xref=True)
597         self.assertEqual(1, len(apps))
598         os.remove(testfile)
599
600         # test using internal template
601         apps = fdroidserver.metadata.read_metadata(xref=True)
602         self.assertEqual(0, len(apps))
603         fdroidserver.update.create_metadata_from_template(apk)
604         self.assertTrue(os.path.exists(testfile))
605         apps = fdroidserver.metadata.read_metadata(xref=True)
606         self.assertEqual(1, len(apps))
607         for app in apps.values():
608             self.assertEqual('urzip', app['Name'])
609             self.assertEqual(1, len(app['Categories']))
610             break
611
612         # test using external template.yml
613         os.remove(testfile)
614         self.assertFalse(os.path.exists(testfile))
615         shutil.copy(os.path.join(localmodule, 'examples', 'template.yml'), tmptestsdir)
616         fdroidserver.update.create_metadata_from_template(apk)
617         self.assertTrue(os.path.exists(testfile))
618         apps = fdroidserver.metadata.read_metadata(xref=True)
619         self.assertEqual(1, len(apps))
620         for app in apps.values():
621             self.assertEqual('urzip', app['Name'])
622             self.assertEqual(1, len(app['Categories']))
623             self.assertEqual('Internet', app['Categories'][0])
624             break
625         with open(testfile) as fp:
626             data = yaml.load(fp)
627         self.assertEqual('urzip', data['Name'])
628         self.assertEqual('urzip', data['Summary'])
629
630     def test_has_known_vulnerability(self):
631         good = [
632             'org.bitbucket.tickytacky.mirrormirror_1.apk',
633             'org.bitbucket.tickytacky.mirrormirror_2.apk',
634             'org.bitbucket.tickytacky.mirrormirror_3.apk',
635             'org.bitbucket.tickytacky.mirrormirror_4.apk',
636             'org.dyndns.fules.ck_20.apk',
637             'urzip.apk',
638             'urzip-badcert.apk',
639             'urzip-badsig.apk',
640             'urzip-release.apk',
641             'urzip-release-unsigned.apk',
642             'repo/com.politedroid_3.apk',
643             'repo/com.politedroid_4.apk',
644             'repo/com.politedroid_5.apk',
645             'repo/com.politedroid_6.apk',
646             'repo/obb.main.oldversion_1444412523.apk',
647             'repo/obb.mainpatch.current_1619_another-release-key.apk',
648             'repo/obb.mainpatch.current_1619.apk',
649             'repo/obb.main.twoversions_1101613.apk',
650             'repo/obb.main.twoversions_1101615.apk',
651             'repo/obb.main.twoversions_1101617.apk',
652             'repo/urzip-; Рахма́нинов, [rɐxˈmanʲɪnəf] سيرجي_رخمانينوف 谢尔盖·.apk',
653         ]
654         for f in good:
655             self.assertFalse(fdroidserver.update.has_known_vulnerability(f))
656         with self.assertRaises(fdroidserver.exception.FDroidException):
657             fdroidserver.update.has_known_vulnerability('janus.apk')
658
659     def test_get_apk_icon_when_src_is_none(self):
660         config = dict()
661         fdroidserver.common.fill_config_defaults(config)
662         fdroidserver.common.config = config
663         fdroidserver.update.config = config
664
665         # pylint: disable=protected-access
666         icons_src = fdroidserver.update._get_apk_icons_src('urzip-release.apk', None)
667         assert icons_src == {}
668
669
670 if __name__ == "__main__":
671     parser = optparse.OptionParser()
672     parser.add_option("-v", "--verbose", action="store_true", default=False,
673                       help="Spew out even more information than normal")
674     (fdroidserver.common.options, args) = parser.parse_args(['--verbose'])
675
676     newSuite = unittest.TestSuite()
677     newSuite.addTest(unittest.makeSuite(UpdateTest))
678     unittest.main(failfast=False)