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