package org.commcare.android.tests.application;
import android.text.format.DateUtils;
import org.commcare.CommCareApp;
import org.commcare.CommCareApplication;
import org.commcare.CommCareTestApplication;
import org.commcare.android.CommCareTestRunner;
import org.commcare.android.util.TestAppInstaller;
import org.commcare.engine.resource.AppInstallStatus;
import org.commcare.engine.resource.ResourceInstallUtils;
import org.commcare.preferences.CommCarePreferences;
import org.commcare.suite.model.Profile;
import org.commcare.tasks.ResultAndError;
import org.commcare.tasks.TaskListener;
import org.commcare.tasks.TaskListenerRegistrationException;
import org.commcare.tasks.UpdateTask;
import org.commcare.utils.PendingCalcs;
import org.joda.time.DateTime;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import java.util.Calendar;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
/**
* Test auto-update triggering logic and update downloading retry logic once
* the auto-update is triggered.
*
* @author Phillip Mates (pmates@dimagi.com).
*/
@Config(application = CommCareTestApplication.class)
@RunWith(CommCareTestRunner.class)
public class AutoUpdateTest {
private final static String REF_BASE_DIR = "jr://resource/commcare-apps/update_tests/";
private final static String username = "test";
private final static String password = "123";
/**
* Check that logging out after an auto-update check failed once should
* trigger the auto-update to resume.
*/
@Test
public void testAppAutoUpdateLogoutRetry() {
installBaseApp();
CommCareApp app = CommCareApplication.instance().getCurrentApp();
Assert.assertFalse(ResourceInstallUtils.shouldAutoUpdateResume(app));
String profileOfInvalidApp = buildResourceRef("invalid_update", "profile.ccpr");
// necessary because retry auto-update task reads from the app's default profile
setAppsDefaultProfile(profileOfInvalidApp);
// try to update to an app with syntax errors
UpdateTask updateTask = UpdateTask.getNewInstance();
updateTask.startPinnedNotification(RuntimeEnvironment.application);
updateTask.setAsAutoUpdate();
try {
TaskListener<Integer, ResultAndError<AppInstallStatus>> listener =
logOutAndInOnCompletionListener(
new ResultAndError<>(AppInstallStatus.MissingResourcesWithMessage));
updateTask.registerTaskListener(listener);
} catch (TaskListenerRegistrationException e) {
fail("failed to register listener for update task");
}
updateTask.execute(profileOfInvalidApp);
Robolectric.flushBackgroundThreadScheduler();
Robolectric.flushForegroundThreadScheduler();
// auto-update should now want to resume
Assert.assertTrue(ResourceInstallUtils.shouldAutoUpdateResume(app));
updateTask.clearTaskInstance();
CommCareApplication.instance().closeUserSession();
}
private void setAppsDefaultProfile(String ref) {
CommCareApp app = CommCareApplication.instance().getCurrentApp();
ResourceInstallUtils.updateProfileRef(app.getAppPreferences(), ref, null);
}
private void installBaseApp() {
TestAppInstaller.installAppAndLogin(
buildResourceRef("base_app", "profile.ccpr"),
username, password);
Profile p = CommCareApplication.instance().getCommCarePlatform().getCurrentProfile();
Assert.assertTrue(p.getVersion() == 6);
}
private String buildResourceRef(String app, String resource) {
return REF_BASE_DIR + app + "/" + resource;
}
private TaskListener<Integer, ResultAndError<AppInstallStatus>> logOutAndInOnCompletionListener(final ResultAndError<AppInstallStatus> expectedResult) {
return new TaskListener<Integer, ResultAndError<AppInstallStatus>>() {
@Override
public void handleTaskUpdate(Integer... updateVals) {
}
@Override
public void handleTaskCompletion(ResultAndError<AppInstallStatus> result) {
assertEquals(expectedResult.data, result.data);
logoutAndIntoApp();
}
@Override
public void handleTaskCancellation() {
}
};
}
private void logoutAndIntoApp() {
CommCareApplication.instance().closeUserSession();
Robolectric.flushBackgroundThreadScheduler();
Robolectric.flushForegroundThreadScheduler();
TestAppInstaller.login(username, password);
}
/**
* Test the auto-update pending calculations, which have several edge
* cases.
*/
@Test
public void testAutoUpdateCalc() {
// should be ready for update if last check was 3 days ago
long checkedThreeDaysAgo = DateTime.now().minusDays(3).getMillis();
Assert.assertTrue(PendingCalcs.isTimeForAutoUpdateCheck(checkedThreeDaysAgo,
CommCarePreferences.FREQUENCY_DAILY));
// shouldn't be ready for update if last check was 3 hours ago
long checkedThreeHoursAgo = DateTime.now().minusHours(3).getMillis();
if (isSameDayAsNow(checkedThreeHoursAgo)) {
Assert.assertFalse(PendingCalcs.isTimeForAutoUpdateCheck(checkedThreeHoursAgo,
CommCarePreferences.FREQUENCY_DAILY));
} else {
Assert.assertTrue(PendingCalcs.isTimeForAutoUpdateCheck(checkedThreeHoursAgo,
CommCarePreferences.FREQUENCY_DAILY));
}
// test different calendar day less than 24 hours ago trigger when
// checking every day
DateTime yesterdayNearMidnight = new DateTime();
yesterdayNearMidnight =
yesterdayNearMidnight.minusDays(1).withTimeAtStartOfDay().plusHours(23);
DateTime now = new DateTime();
long diff = yesterdayNearMidnight.minus(now.getMillis()).getMillis();
Assert.assertTrue(diff < DateUtils.DAY_IN_MILLIS);
Assert.assertTrue(PendingCalcs.isTimeForAutoUpdateCheck(yesterdayNearMidnight.getMillis(),
CommCarePreferences.FREQUENCY_DAILY));
// test timeshift a couple of hours in the future, shouldn't be enough
// to warrant a update trigger
long hoursInTheFuture = DateTime.now().plusHours(2).getMillis();
Assert.assertFalse(PendingCalcs.isTimeForAutoUpdateCheck(hoursInTheFuture,
CommCarePreferences.FREQUENCY_DAILY));
// test timeshift where if we last checked more than one day in the
// future then we trigger
long daysLater = DateTime.now().plusDays(2).getMillis();
Assert.assertTrue(PendingCalcs.isTimeForAutoUpdateCheck(daysLater,
CommCarePreferences.FREQUENCY_DAILY));
long weekLater = DateTime.now().plusWeeks(1).getMillis();
Assert.assertTrue(PendingCalcs.isTimeForAutoUpdateCheck(weekLater,
CommCarePreferences.FREQUENCY_DAILY));
}
private static boolean isSameDayAsNow(long checkTime) {
return getDayOfWeek(checkTime) == Calendar.getInstance().get(Calendar.DAY_OF_WEEK);
}
private static int getDayOfWeek(long time) {
Calendar lastRestoreCalendar = Calendar.getInstance();
lastRestoreCalendar.setTimeInMillis(time);
return lastRestoreCalendar.get(Calendar.DAY_OF_WEEK);
}
}