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