diff options
Diffstat (limited to 'app/src/androidTest/java')
4 files changed, 288 insertions, 22 deletions
diff --git a/app/src/androidTest/java/de/test/antennapod/storage/AutoDownloadTest.java b/app/src/androidTest/java/de/test/antennapod/storage/AutoDownloadTest.java new file mode 100644 index 000000000..c6beb2d42 --- /dev/null +++ b/app/src/androidTest/java/de/test/antennapod/storage/AutoDownloadTest.java @@ -0,0 +1,168 @@ +package de.test.antennapod.storage; + +import android.content.Context; +import android.content.Intent; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.test.core.app.ApplicationProvider; + +import org.awaitility.Awaitility; +import org.awaitility.core.ConditionTimeoutException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.List; + +import de.danoeh.antennapod.core.ClientConfig; +import de.danoeh.antennapod.core.DBTasksCallbacks; +import de.danoeh.antennapod.core.feed.FeedItem; +import de.danoeh.antennapod.core.feed.FeedMedia; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.service.playback.PlaybackService; +import de.danoeh.antennapod.core.storage.AutomaticDownloadAlgorithm; +import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.storage.DBTasks; +import de.danoeh.antennapod.core.storage.EpisodeCleanupAlgorithm; +import de.danoeh.antennapod.core.storage.PodDBAdapter; +import de.danoeh.antennapod.core.util.playback.Playable; +import de.test.antennapod.ui.UITestUtils; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class AutoDownloadTest { + + private Context context; + private UITestUtils stubFeedsServer; + + private DBTasksCallbacks dbTasksCallbacksOrig; + + @Before + public void setUp() throws Exception { + context = ApplicationProvider.getApplicationContext(); + + stubFeedsServer = new UITestUtils(context);; + stubFeedsServer.setup(); + + dbTasksCallbacksOrig = ClientConfig.dbTasksCallbacks; + + // create new database + PodDBAdapter.init(context); + PodDBAdapter.deleteDatabase(); + PodDBAdapter adapter = PodDBAdapter.getInstance(); + adapter.open(); + adapter.close(); + } + + @After + public void tearDown() throws Exception { + stubFeedsServer.tearDown(); + + context.sendBroadcast(new Intent(PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE)); + Awaitility.await().until(() -> !PlaybackService.isRunning); + + ClientConfig.dbTasksCallbacks = dbTasksCallbacksOrig; + } + + /** + * A cross-functional test, ensuring playback's behavior works with Auto Download in boundary condition. + * + * Scenario: + * - For setting enqueue location AFTER_CURRENTLY_PLAYING + * - when playback of an episode is complete and the app advances to the next episode (continuous playback on) + * - when automatic download kicks in, + * - ensure the next episode is the current playing one, needed for AFTER_CURRENTLY_PLAYING enqueue location. + */ + @Test + public void downloadsEnqueuedToAfterCurrent_CurrentAdvancedToNextOnPlaybackComplete() throws Exception { + UserPreferences.setFollowQueue(true); // continuous playback + + // Setup: feeds and queue + // downloads 3 of them, leave some in new state (auto-downloadable) + stubFeedsServer.addLocalFeedData(false); + List<FeedItem> queue = DBReader.getQueue(); + assertTrue(queue.size() > 1); + FeedItem item0 = queue.get(0); + FeedItem item1 = queue.get(1); + + // Setup: enable automatic download + // it is not needed, as the actual automatic download is stubbed. + StubDownloadAlgorithm stubDownloadAlgorithm = new StubDownloadAlgorithm(); + useDownloadAlgorithm(stubDownloadAlgorithm); + + // Actual test + // Play the first one in the queue + playEpisode(item0); + + try { + // when playback is complete, advances to the next one, and auto download kicks in, + // ensure that currently playing has been advanced to the next one by this point. + Awaitility.await("advanced to the next episode") + .atMost(6000, MILLISECONDS) // the test mp3 media is 3-second long. twice should be enough + .until(() -> item1.equals(stubDownloadAlgorithm.getCurrentlyPlayingAtDownload())); + } catch (ConditionTimeoutException cte) { + FeedItem actual = stubDownloadAlgorithm.getCurrentlyPlayingAtDownload(); + fail("when auto download is triggered, the next episode should be playing: (" + + item1.getId() + ", " + item1.getTitle() + ") . " + + "Actual playing: (" + + (actual == null ? "" : actual.getId() + ", " + actual.getTitle()) + ")" + ); + } + + } + + private void playEpisode(@NonNull FeedItem item) { + FeedMedia media = item.getMedia(); + DBTasks.playMedia(context, media, false, true, true); + Awaitility.await("episode is playing") + .atMost(1000, MILLISECONDS) + .until(() -> item.equals(getCurrentlyPlaying())); + } + + private FeedItem getCurrentlyPlaying() { + Playable playable = Playable.PlayableUtils.createInstanceFromPreferences(context); + if (playable == null) { + return null; + } + return ((FeedMedia)playable).getItem(); + } + + private void useDownloadAlgorithm(final AutomaticDownloadAlgorithm downloadAlgorithm) { + DBTasksCallbacks dbTasksCallbacksStub = new DBTasksCallbacks() { + @Override + public AutomaticDownloadAlgorithm getAutomaticDownloadAlgorithm() { + return downloadAlgorithm; + } + + @Override + public EpisodeCleanupAlgorithm getEpisodeCacheCleanupAlgorithm() { + return dbTasksCallbacksOrig.getEpisodeCacheCleanupAlgorithm(); + } + }; + ClientConfig.dbTasksCallbacks = dbTasksCallbacksStub; + } + + private class StubDownloadAlgorithm implements AutomaticDownloadAlgorithm { + @Nullable + private FeedItem currentlyPlaying; + + @Override + public Runnable autoDownloadUndownloadedItems(Context context) { + return () -> { + if (currentlyPlaying == null) { + currentlyPlaying = getCurrentlyPlaying(); + } else { + throw new AssertionError("Stub automatic download should be invoked once and only once"); + } + }; + } + + @Nullable + FeedItem getCurrentlyPlayingAtDownload() { + return currentlyPlaying; + } + } +} diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java b/app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java index 55ea16f13..cce4e5111 100644 --- a/app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java +++ b/app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java @@ -2,26 +2,31 @@ package de.test.antennapod.storage; import android.content.Context; +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.List; -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.LargeTest; -import androidx.test.filters.SmallTest; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBTasks; +import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.PodDBAdapter; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import static de.danoeh.antennapod.core.util.FeedItemUtil.getIdList; import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -179,4 +184,82 @@ public class DBTasksTest { lastDate = item.getPubDate(); } } + + @Test + public void testAddQueueItemsInDownload_EnqueueEnabled() throws Exception { + // Setup test data / environment + UserPreferences.setEnqueueDownloadedEpisodes(true); + UserPreferences.setEnqueueLocation(UserPreferences.EnqueueLocation.BACK); + + List<FeedItem> fis1 = createSavedFeed("Feed 1", 2).getItems(); + List<FeedItem> fis2 = createSavedFeed("Feed 2", 3).getItems(); + + DBWriter.addQueueItem(context, fis1.get(0), fis2.get(0)).get(); + // the first item fis1.get(0) is already in the queue + FeedItem[] itemsToDownload = new FeedItem[]{ fis1.get(0), fis1.get(1), fis2.get(2), fis2.get(1) }; + + // Expectations: + List<FeedItem> expectedEnqueued = Arrays.asList(fis1.get(1), fis2.get(2), fis2.get(1)); + List<FeedItem> expectedQueue = new ArrayList<>(); + expectedQueue.addAll(DBReader.getQueue()); + expectedQueue.addAll(expectedEnqueued); + + // Run actual test and assert results + List<? extends FeedItem> actualEnqueued = + DBTasks.enqueueFeedItemsToDownload(context, itemsToDownload); + + assertEqualsByIds("Only items not in the queue are enqueued", expectedEnqueued, actualEnqueued); + assertEqualsByIds("Queue has new items appended", expectedQueue, DBReader.getQueue()); + } + + @Test + public void testAddQueueItemsInDownload_EnqueueDisabled() throws Exception { + // Setup test data / environment + UserPreferences.setEnqueueDownloadedEpisodes(false); + + List<FeedItem> fis1 = createSavedFeed("Feed 1", 2).getItems(); + List<FeedItem> fis2 = createSavedFeed("Feed 2", 3).getItems(); + + DBWriter.addQueueItem(context, fis1.get(0), fis2.get(0)).get(); + FeedItem[] itemsToDownload = new FeedItem[]{ fis1.get(0), fis1.get(1), fis2.get(2), fis2.get(1) }; + + // Expectations: + List<FeedItem> expectedEnqueued = Collections.emptyList(); + List<FeedItem> expectedQueue = DBReader.getQueue(); + + // Run actual test and assert results + List<? extends FeedItem> actualEnqueued = + DBTasks.enqueueFeedItemsToDownload(context, itemsToDownload); + + assertEqualsByIds("No item is enqueued", expectedEnqueued, actualEnqueued); + assertEqualsByIds("Queue is unchanged", expectedQueue, DBReader.getQueue()); + } + + private void assertEqualsByIds(String msg, List<? extends FeedItem> expected, List<? extends FeedItem> actual) { + // assert only the IDs, so that any differences are easily to spot. + List<Long> expectedIds = getIdList(expected); + List<Long> actualIds = getIdList(actual); + assertEquals(msg, expectedIds, actualIds); + } + + private Feed createSavedFeed(String title, int numFeedItems) { + final Feed feed = new Feed("url", null, title); + + if (numFeedItems > 0) { + List<FeedItem> items = new ArrayList<>(numFeedItems); + for (int i = 1; i <= numFeedItems; i++) { + FeedItem item = new FeedItem(0, "item " + i + " of " + title, "id", "link", + new Date(), FeedItem.UNPLAYED, feed); + items.add(item); + } + feed.setItems(items); + } + + PodDBAdapter adapter = PodDBAdapter.getInstance(); + adapter.open(); + adapter.setCompleteFeed(feed); + adapter.close(); + return feed; + } + } diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBWriterTest.java b/app/src/androidTest/java/de/test/antennapod/storage/DBWriterTest.java index 42f4413d3..1e13bd5c3 100644 --- a/app/src/androidTest/java/de/test/antennapod/storage/DBWriterTest.java +++ b/app/src/androidTest/java/de/test/antennapod/storage/DBWriterTest.java @@ -4,12 +4,15 @@ import android.content.Context; import android.content.SharedPreferences; import android.database.Cursor; import android.preference.PreferenceManager; +import android.util.Log; + import androidx.test.InstrumentationRegistry; -import androidx.test.filters.LargeTest; import androidx.test.filters.MediumTest; -import android.util.Log; import org.awaitility.Awaitility; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; import java.io.File; import java.io.IOException; @@ -29,9 +32,7 @@ import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.PodDBAdapter; import de.danoeh.antennapod.core.util.Consumer; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import de.danoeh.antennapod.core.util.FeedItemUtil; import static androidx.test.InstrumentationRegistry.getInstrumentation; import static org.junit.Assert.assertEquals; @@ -475,11 +476,12 @@ public class DBWriterTest { assertFalse(OLD_DATE == media.getPlaybackCompletionDate().getTime()); } - private Feed queueTestSetupMultipleItems(final int NUM_ITEMS) throws InterruptedException, ExecutionException, TimeoutException { + private Feed queueTestSetupMultipleItems(final int numItems) throws InterruptedException, ExecutionException, TimeoutException { final Context context = getInstrumentation().getTargetContext(); + UserPreferences.setEnqueueLocation(UserPreferences.EnqueueLocation.BACK); Feed feed = new Feed("url", null, "title"); feed.setItems(new ArrayList<>()); - for (int i = 0; i < NUM_ITEMS; i++) { + for (int i = 0; i < numItems; i++) { FeedItem item = new FeedItem(0, "title " + i, "id " + i, "link " + i, new Date(), FeedItem.PLAYED, feed); feed.getItems().add(item); } @@ -573,12 +575,16 @@ public class DBWriterTest { Cursor cursor = adapter.getQueueIDCursor(); assertTrue(cursor.moveToFirst()); assertTrue(cursor.getCount() == NUM_ITEMS); + List<Long> expectedIds = FeedItemUtil.getIdList(feed.getItems()); + List<Long> actualIds = new ArrayList<>(); for (int i = 0; i < NUM_ITEMS; i++) { assertTrue(cursor.moveToPosition(i)); - assertTrue(cursor.getLong(0) == feed.getItems().get(i).getId()); + actualIds.add(cursor.getLong(0)); } cursor.close(); adapter.close(); + assertEquals("Bulk add to queue: result order should be the same as the order given", + expectedIds, actualIds); } @Test diff --git a/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java b/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java index 65bc7d745..f22e4b426 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java @@ -4,13 +4,14 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Resources; import android.preference.PreferenceManager; -import androidx.test.filters.LargeTest; +import androidx.annotation.StringRes; +import androidx.test.filters.LargeTest; import androidx.test.rule.ActivityTestRule; + import com.robotium.solo.Solo; import com.robotium.solo.Timeout; -import de.test.antennapod.EspressoTestUtils; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -21,6 +22,7 @@ import java.util.concurrent.TimeUnit; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.PreferenceActivity; import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.preferences.UserPreferences.EnqueueLocation; import de.danoeh.antennapod.core.storage.APCleanupAlgorithm; import de.danoeh.antennapod.core.storage.APNullCleanupAlgorithm; import de.danoeh.antennapod.core.storage.APQueueCleanupAlgorithm; @@ -28,6 +30,7 @@ import de.danoeh.antennapod.core.storage.EpisodeCleanupAlgorithm; import de.danoeh.antennapod.fragment.EpisodesFragment; import de.danoeh.antennapod.fragment.QueueFragment; import de.danoeh.antennapod.fragment.SubscriptionFragment; +import de.test.antennapod.EspressoTestUtils; import static androidx.test.InstrumentationRegistry.getInstrumentation; import static androidx.test.espresso.Espresso.onView; @@ -126,13 +129,19 @@ public class PreferencesTest { } @Test - public void testEnqueueAtFront() { + public void testEnqueueLocation() { clickPreference(R.string.playback_pref); - final boolean enqueueAtFront = UserPreferences.enqueueAtFront(); - clickPreference(R.string.pref_queueAddToFront_title); - assertTrue(solo.waitForCondition(() -> enqueueAtFront != UserPreferences.enqueueAtFront(), Timeout.getLargeTimeout())); - clickPreference(R.string.pref_queueAddToFront_title); - assertTrue(solo.waitForCondition(() -> enqueueAtFront == UserPreferences.enqueueAtFront(), Timeout.getLargeTimeout())); + doTestEnqueueLocation(R.string.enqueue_location_after_current, EnqueueLocation.AFTER_CURRENTLY_PLAYING); + doTestEnqueueLocation(R.string.enqueue_location_front, EnqueueLocation.FRONT); + doTestEnqueueLocation(R.string.enqueue_location_back, EnqueueLocation.BACK); + } + + private void doTestEnqueueLocation(@StringRes int optionResId, EnqueueLocation expected) { + clickPreference(R.string.pref_enqueue_location_title); + onView(withText(optionResId)).perform(click()); + assertTrue(solo.waitForCondition( + () -> expected == UserPreferences.getEnqueueLocation(), + Timeout.getLargeTimeout())); } @Test |