From cff209cd44b13dd16b63c9c080d6217ad98d1e55 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Wed, 7 Sep 2016 22:02:57 +1000 Subject: [PATCH] Replicated issue #763 in a test case --- .../org/fdroid/fdroid/Issue763MultiRepo.java | 138 ++++++++++++++++++ .../fdroid/fdroid/MultiRepoUpdaterTest.java | 18 ++- app/src/test/resources/index.antox.jar | Bin 0 -> 5206 bytes app/src/test/resources/index.microg.jar | Bin 0 -> 5132 bytes 4 files changed, 151 insertions(+), 5 deletions(-) create mode 100644 app/src/test/java/org/fdroid/fdroid/Issue763MultiRepo.java create mode 100644 app/src/test/resources/index.antox.jar create mode 100644 app/src/test/resources/index.microg.jar diff --git a/app/src/test/java/org/fdroid/fdroid/Issue763MultiRepo.java b/app/src/test/java/org/fdroid/fdroid/Issue763MultiRepo.java new file mode 100644 index 000000000..8df75aedb --- /dev/null +++ b/app/src/test/java/org/fdroid/fdroid/Issue763MultiRepo.java @@ -0,0 +1,138 @@ +package org.fdroid.fdroid; + +import android.content.ContentValues; + +import org.fdroid.fdroid.data.Apk; +import org.fdroid.fdroid.data.ApkProvider; +import org.fdroid.fdroid.data.Repo; +import org.fdroid.fdroid.data.RepoProvider; +import org.fdroid.fdroid.data.Schema; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricGradleTestRunner; +import org.robolectric.annotation.Config; + +import java.util.List; + +import static org.junit.Assert.assertEquals; + +// TODO: Use sdk=24 when Robolectric supports this +@Config(constants = BuildConfig.class, sdk = 23) +@RunWith(RobolectricGradleTestRunner.class) +public class Issue763MultiRepo extends MultiRepoUpdaterTest { + + private Repo microGRepo; + private Repo antoxRepo; + + @Before + public void setup() { + String microGCert = "308202ed308201d5a003020102020426ffa009300d06092a864886f70d01010b05003027310b300906035504061302444531183016060355040a130f4e4f47415050532050726f6a656374301e170d3132313030363132303533325a170d3337303933303132303533325a3027310b300906035504061302444531183016060355040a130f4e4f47415050532050726f6a65637430820122300d06092a864886f70d01010105000382010f003082010a02820101009a8d2a5336b0eaaad89ce447828c7753b157459b79e3215dc962ca48f58c2cd7650df67d2dd7bda0880c682791f32b35c504e43e77b43c3e4e541f86e35a8293a54fb46e6b16af54d3a4eda458f1a7c8bc1b7479861ca7043337180e40079d9cdccb7e051ada9b6c88c9ec635541e2ebf0842521c3024c826f6fd6db6fd117c74e859d5af4db04448965ab5469b71ce719939a06ef30580f50febf96c474a7d265bb63f86a822ff7b643de6b76e966a18553c2858416cf3309dd24278374bdd82b4404ef6f7f122cec93859351fc6e5ea947e3ceb9d67374fe970e593e5cd05c905e1d24f5a5484f4aadef766e498adf64f7cf04bddd602ae8137b6eea40722d0203010001a321301f301d0603551d0e04160414110b7aa9ebc840b20399f69a431f4dba6ac42a64300d06092a864886f70d01010b0500038201010007c32ad893349cf86952fb5a49cfdc9b13f5e3c800aece77b2e7e0e9c83e34052f140f357ec7e6f4b432dc1ed542218a14835acd2df2deea7efd3fd5e8f1c34e1fb39ec6a427c6e6f4178b609b369040ac1f8844b789f3694dc640de06e44b247afed11637173f36f5886170fafd74954049858c6096308fc93c1bc4dd5685fa7a1f982a422f2a3b36baa8c9500474cf2af91c39cbec1bc898d10194d368aa5e91f1137ec115087c31962d8f76cd120d28c249cf76f4c70f5baa08c70a7234ce4123be080cee789477401965cfe537b924ef36747e8caca62dfefdd1a6288dcb1c4fd2aaa6131a7ad254e9742022cfd597d2ca5c660ce9e41ff537e5a4041e37"; + microGRepo = createRepo("MicroG", "https://microg.org/fdroid/repo", context, microGCert); + + String antoxCert = "308204f1308202d9a0030201020204565444c6300d06092a864886f70d01010b050030293110300e060355040b1307462d44726f6964311530130603550403130c706b672e746f782e63686174301e170d3136303131323037353333395a170d3433303533303037353333395a30293110300e060355040b1307462d44726f6964311530130603550403130c706b672e746f782e6368617430820222300d06092a864886f70d01010105000382020f003082020a0282020100a16c61417d25545d681d7c01acea881f268bb4d708099aa12143f12d24a18afe120f532901efc3a26137915bba5ffd4e8f0d21783965b2c207593b44002e6ed7ca6cbb124829c134950c9c76388d70cdb5c1ac37581687f1a4a51ce7d5c0a21ab000bae2c14572d26693de8b33726852e262ec9c85bed5d6a1e236977862d8e796a3722bc69346ae27951527963165469ab4ec9c8a38ccabd4c3de718eeaa8ff054bcd04374ac46af7cf011d97bd2625c4c7f6c2851b0cab8446eaf2f94a2b1506aafdfb192d0a31ede495fd4cc4e122f92daa500806d29aa3f63e51dbf6f00fb37979b4b70543f019d55e95173165378983517f2d2811fe79d491f09dc2568794c3a09f539986d4489b939d65c26a83f6b6165976c00cd648d081afc3a5eeabd1c4e3c0ae42d92197f4086caca8bcfe939036d02f84e815b95842da27daea297bd098507f806012fe0cae2d4dd38bd876a7efb0c173bfae260e820d56e7026afe4f8806d12ffa2e75da33d178472625414d2ed3e0fe9ef1f2ba0b26b877960bea54c8e32f36540758712b40775b56ef1149db3df17202e5163733df12a48011f9077e34aebeba2fe11dc578ce12bd26a30a1d458d0172378b530de118dc46823e17f6bbb7d0163483531e12d416f6f9d90d63b6c248a30058fb1c0b1c78a50601fdad0ca22dbfa470059251f37ce558a7bacf2ebad2d810049717aee16c8d530203010001a321301f301d0603551d0e0416041419f45074c35bffc3bdc4ebee34966db52cfcb54e300d06092a864886f70d01010b05000382020100426ce3f1a5944dbd41e6abc5348e32f220f46b58a8794091e5de1f6248af18e42c0b1ed9c5196b27c9fbd0aa59170653f3044b0f8cd60f027f888be91fdb52fb7e6f3c125bbbf968ca1d43fede1a47a82ebd89ef37e2abc5931f2475ed7c3b95707c75f1e90e0f08460288ee090e5136a4dd682ddf8755b6d2c8e8ff58037865d69f198599371cca60e6ab8cac7c35de1edfaff2730a8c91489e30c7d770fe7b2299b41229d27989bf20260d43c8c077e53ec11e1a17b8879ec8c995fb9fa178d6ffdd6629c3104601368cc76e0f10f7ad3a2a729f92d219700dd44a8621a8102ec61e28d534b518633b4edb125966e80bf006f0e1258f7bd36357ab2ebdb8fb40fb1616f75bf5db2689b3910dff266084832159afc1571454a8070fe2a02389254e5f3cbab933a57117cb76bb615c4180c88f3bd04c6f23ea75ac05ab81dd4ddd2c9f2b3eb94d54682f12c7838612f434b00a6da678e9e82b5b4a18c037929a622773c6bc4bcf1eb45872c998248d98812d6be1a77d0d70182b9b2296b8802fbd0fb2f04b78bfafbaae756940f777ae43f63e8f47e97618063fc743ba1dd3d37bb434581fb3487420dd893fa474d87b94923102c12a680ede3107c680be0d8d5e7f89c0033a740f0ba3e1563baddb540f0f9154653b003d9dd3cbdd649e808ecfec9dd8d9949ccabb7b8b7ad287023bc8ca12dad9892158da300a25de07aaca"; + antoxRepo = createRepo("Tox", "https://pkg.tox.chat/fdroid/repo", context, antoxCert); + } + + public void setEnabled(Repo repo, boolean enabled) { + ContentValues values = new ContentValues(1); + values.put(Schema.RepoTable.Cols.IN_USE, enabled); + RepoProvider.Helper.update(context, repo, values); + } + + @Test + public void antoxRepo() throws RepoUpdater.UpdateException { + assertAntoxEmpty(); + setEnabled(microGRepo, true); + updateAntox(); + assertAntoxExists(); + } + + private void updateAntox() throws RepoUpdater.UpdateException { + updateRepo(new RepoUpdater(context, antoxRepo), "index.antox.jar"); + } + + @Test + public void microGRepo() throws RepoUpdater.UpdateException { + assertMicroGEmpty(); + setEnabled(microGRepo, true); + updateMicroG(); + assertMicroGExists(); + } + + private void updateMicroG() throws RepoUpdater.UpdateException { + updateRepo(new RepoUpdater(context, microGRepo), "index.microg.jar"); + } + + @Test + public void antoxAndMicroG() throws RepoUpdater.UpdateException { + assertMicroGEmpty(); + assertAntoxEmpty(); + + setEnabled(microGRepo, true); + setEnabled(antoxRepo, true); + updateMicroG(); + updateAntox(); + assertMicroGExists(); + assertAntoxExists(); + + setEnabled(microGRepo, false); + RepoProvider.Helper.purgeApps(context, microGRepo); + assertMicroGEmpty(); + assertAntoxExists(); + + setEnabled(microGRepo, true); + updateMicroG(); + assertMicroGExists(); + assertAntoxExists(); + + setEnabled(antoxRepo, false); + RepoProvider.Helper.purgeApps(context, antoxRepo); + assertMicroGExists(); + assertAntoxEmpty(); + + setEnabled(antoxRepo, true); + updateAntox(); + assertMicroGExists(); + assertAntoxExists(); + } + + private void assertAntoxEmpty() { + List actualApksBeforeUpdate = ApkProvider.Helper.findByRepo(context, antoxRepo, Schema.ApkTable.Cols.ALL); + assertEquals(0, actualApksBeforeUpdate.size()); + } + + private void assertMicroGEmpty() { + List actualApksBeforeUpdate = ApkProvider.Helper.findByRepo(context, microGRepo, Schema.ApkTable.Cols.ALL); + assertEquals(0, actualApksBeforeUpdate.size()); + } + + private void assertAntoxExists() { + String packageName = "chat.tox.antox"; + List actualApksAfterUpdate = ApkProvider.Helper.findByRepo(context, antoxRepo, Schema.ApkTable.Cols.ALL); + int[] expectedVersions = new int[] {15421}; + + assertApp(packageName, expectedVersions); + assertApksExist(actualApksAfterUpdate, packageName, expectedVersions); + } + + private void assertMicroGExists() { + List actualApksAfterUpdate = ApkProvider.Helper.findByRepo(context, microGRepo, Schema.ApkTable.Cols.ALL); + + String vendingPackage = "com.android.vending"; + int[] expectedVendingVersions = new int[] {1}; + assertApp(vendingPackage, expectedVendingVersions); + assertApksExist(actualApksAfterUpdate, vendingPackage, expectedVendingVersions); + + String gmsPackage = "com.google.android.gms"; + int[] expectedGmsVersions = new int[] {9452267, 9452266, 9452265, 9258262, 9258259, 9258258, 8492252, }; + assertApp(gmsPackage, expectedGmsVersions); + assertApksExist(actualApksAfterUpdate, gmsPackage, expectedGmsVersions); + + String gsfPackage = "com.google.android.gsf"; + int[] expectedGsfVersions = new int[] {8}; + assertApp(gsfPackage, expectedGsfVersions); + assertApksExist(actualApksAfterUpdate, gsfPackage, expectedGsfVersions); + } + +} diff --git a/app/src/test/java/org/fdroid/fdroid/MultiRepoUpdaterTest.java b/app/src/test/java/org/fdroid/fdroid/MultiRepoUpdaterTest.java index 9b694e863..f4c529059 100644 --- a/app/src/test/java/org/fdroid/fdroid/MultiRepoUpdaterTest.java +++ b/app/src/test/java/org/fdroid/fdroid/MultiRepoUpdaterTest.java @@ -152,9 +152,13 @@ public abstract class MultiRepoUpdaterTest extends FDroidProviderTest { } } - private RepoUpdater createUpdater(String name, String uri, Context context) { + protected Repo createRepo(String name, String uri, Context context) { + return createRepo(name, uri, context, PUB_KEY); + } + + protected Repo createRepo(String name, String uri, Context context, String signingCert) { Repo repo = new Repo(); - repo.signingCertificate = PUB_KEY; + repo.signingCertificate = signingCert; repo.address = uri; repo.name = name; @@ -167,7 +171,11 @@ public abstract class MultiRepoUpdaterTest extends FDroidProviderTest { // Need to reload the repo based on address so that it includes the primary key from // the database. - return new RepoUpdater(context, RepoProvider.Helper.findByAddress(context, repo.address)); + return RepoProvider.Helper.findByAddress(context, repo.address); + } + + protected RepoUpdater createUpdater(String name, String uri, Context context) { + return new RepoUpdater(context, createRepo(name, uri, context)); } protected boolean updateConflicting() throws UpdateException { @@ -182,13 +190,13 @@ public abstract class MultiRepoUpdaterTest extends FDroidProviderTest { return updateRepo(createUpdater(REPO_ARCHIVE, REPO_ARCHIVE_URI, context), "multiRepo.archive.jar"); } - private boolean updateRepo(RepoUpdater updater, String indexJarPath) throws UpdateException { + protected boolean updateRepo(RepoUpdater updater, String indexJarPath) throws UpdateException { File indexJar = TestUtils.copyResourceToTempFile(indexJarPath); try { updater.processDownloadedFile(indexJar); } finally { if (indexJar != null && indexJar.exists()) { - assertTrue(indexJar.delete()); + indexJar.delete(); } } return true; diff --git a/app/src/test/resources/index.antox.jar b/app/src/test/resources/index.antox.jar new file mode 100644 index 0000000000000000000000000000000000000000..ab864b28fb6104ddc056e218930041e4a728ea35 GIT binary patch literal 5206 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 literal 0 HcmV?d00001