/*
* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.samples.apps.iosched.sync.userdata.firebase;
import android.content.Context;
import android.content.SharedPreferences;
import android.test.suitebuilder.annotation.SmallTest;
import com.google.samples.apps.iosched.sync.userdata.UserAction;
import com.google.samples.apps.iosched.sync.userdata.util.UserData;
import com.google.samples.apps.iosched.sync.userdata.util.UserDataHelper;
import com.google.samples.apps.iosched.util.FirebaseUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
@RunWith(MockitoJUnitRunner.class)
@SmallTest
public class MergeHelperTest {
// TODO: expand tests to cover all sync scenarios. See b/28324707.
public static final String UID = "uid";
public static final String LOCAL_GCM_KEY = "LOCAL GCM KEY";
public static final String REMOTE_GCM_KEY = "REMOTE GCM KEY";
public static final String LOCAL_VIDEO_ID = "LOCAL VIDEO ID";
public static final String SESSION_ONE_ID = "SESSION ONE ID";
public static final String SESSION_TWO_ID = "SESSION TWO ID";
public static final String REMOTE_VIDEO_ID = "REMOTE VIDEO ID";
public static final Long CURRENT_TIMESTAMP = 1457928631L;
public static final Long TIMESTAMP = CURRENT_TIMESTAMP;
public static final Long STALE_TIMESTAMP = CURRENT_TIMESTAMP - (60 * 60 * 1000);
public static final String LOCAL_SESSION_ID = "LOCAL SESSION ID";
public static final String REMOTE_SESSION_ID = "REMOTE SESSION ID";
/**
* Creates a {@link UserAction} for a viewed video.
*
* @param videoId The ID of the viewed video.
* @param requiresSync Indicates whether the action requires a data sync or not.
* @return The {@link UserAction} for a viewed video.
*/
private static UserAction createViewedVideoAction(String videoId, boolean requiresSync) {
UserAction action = new UserAction();
action.type = UserAction.TYPE.VIEW_VIDEO;
action.videoId = videoId;
action.requiresSync = requiresSync;
return action;
}
/**
* Create a {@link UserAction} for a starred session.
*
* @param sessionId The ID of the starred session.
* @param requiresSync Indicates whether the action requires a data sync or not.
* @return The {@link UserAction} for a starred session.
*/
private static UserAction createAddStarAction(String sessionId, Long timestamp,
boolean requiresSync) {
UserAction action = new UserAction();
action.type = UserAction.TYPE.ADD_STAR;
action.sessionId = sessionId;
action.requiresSync = requiresSync;
action.timestamp = timestamp;
return action;
}
/**
* Create a {@link UserAction} for a starred session.
*
* @param sessionId The ID of the starred session.
* @param requiresSync Indicates whether the action requires a data sync or not.
* @return The {@link UserAction} for a starred session.
*/
private static UserAction createRemoveStarAction(String sessionId, Long timestamp,
boolean requiresSync) {
UserAction action = new UserAction();
action.type = UserAction.TYPE.REMOVE_STAR;
action.sessionId = sessionId;
action.requiresSync = requiresSync;
action.timestamp = timestamp;
return action;
}
/**
* Creates a {@link UserAction} for a session for which feedback was submitted.
*
* @param sessionId The ID of the session for which feedback was submitted.
* @param requiresSync Indicates whether the action requires a data sync or not.
* @return The {@link UserAction} for a feedback submitted session.
*/
private static UserAction createFeedbackSubmittedSessionAction(String sessionId,
boolean requiresSync) {
UserAction action = new UserAction();
action.type = UserAction.TYPE.SUBMIT_FEEDBACK;
action.sessionId = sessionId;
action.requiresSync = requiresSync;
return action;
}
@Mock
private Context mMockContext;
@Mock
private SharedPreferences mMockSharedPreferences;
MergeHelper mHelper;
@Before
public void setUp() {
mHelper = new MergeHelper(new UserData(),
new UserData(), new UserData(), UID);
withLocalGCMKey();
}
@Test
public void mergeGCMKeys_whenRemoteDoesNotHaveKey() {
mHelper.mergeGCMKeys();
assertThatMergedGCMKeyIs(LOCAL_GCM_KEY);
}
@Test
public void mergeGCMKeys_whenRemoteHasKey() {
withRemoteGCMKey();
mHelper.mergeGCMKeys();
assertThatMergedGCMKeyIs(REMOTE_GCM_KEY);
}
@Test
public void mergeUnsyncedActions_localSessionOnly_requiresSync() {
mHelper.mergeUnsyncedActions(withLocalStarredSessionActions(LOCAL_SESSION_ID));
assertThatMergedStarSessionsHas(LOCAL_SESSION_ID);
}
@Test
public void mergeUnsyncedActions_localSessionOnly_noSync() {
mHelper.mergeUnsyncedActions(withLocalStarredSessionActionsNoSync());
assertThatMergedStarSessionsHasNoSessions();
}
@Test
public void mergeUnsyncedActions_remoteSessionOnly() {
withRemoteStarredSession(REMOTE_SESSION_ID);
mHelper.mergeUnsyncedActions(withNoLocalUserActions());
assertThatMergedStarSessionsHas(REMOTE_SESSION_ID);
}
@Test
public void mergeUnsyncedActions_localAndRemoteSessions_differentSessionIDs() {
withRemoteStarredSession(REMOTE_SESSION_ID);
mHelper.mergeUnsyncedActions(withLocalStarredSessionActions(LOCAL_SESSION_ID));
assertThatMergedStarSessionsHas(LOCAL_SESSION_ID);
assertThatMergedStarSessionsHas(REMOTE_SESSION_ID);
}
@Test
public void mergeUnsyncedActions_localRemoveStar_remoteAddStar_localStale() {
mHelper.mergeUnsyncedActions(withLocalRemoveRemoteAddLocalStale());
assertThatMergedStarSessionsHas(SESSION_ONE_ID);
}
@Test
public void mergeUnsyncedActions_localViewedVideosOnly() {
mHelper.mergeUnsyncedActions(withViewedVideoLocalActions());
assertThatMergedViewedVideosHas(LOCAL_VIDEO_ID);
}
@Test
public void mergeUnsyncedActions_localViewedVideosOnly_withoutRequiresSync() {
mHelper.mergeUnsyncedActions(withViewedVideoLocalActionsNoSync());
assertThatMergedUserDataHasNoVideoIds();
}
@Test
public void mergeUnsyncedActions_remoteViewedVideosOnly() {
withRemoteViewedVideo();
mHelper.mergeUnsyncedActions(withNoLocalUserActions());
assertThatMergedViewedVideosHas(REMOTE_VIDEO_ID);
}
@Test
public void mergeUnsyncedActions_localAndRemoteViewedVideos() {
withRemoteViewedVideo();
mHelper.mergeUnsyncedActions(withViewedVideoLocalActions());
assertThatMergedViewedVideosHas(LOCAL_VIDEO_ID);
assertThatMergedViewedVideosHas(REMOTE_VIDEO_ID);
}
@Test
public void mergeUnsyncedActions_localFeedbackSubmittedSessionsOnly() {
mHelper.mergeUnsyncedActions(withLocalFeedbackSubmittedActions());
assertThatMergeFeedbackSubmittedSessionsHas(LOCAL_SESSION_ID);
}
@Test
public void mergeUnsyncedActions_remoteFeedbackSubmittedSessionsOnly() {
withRemoteFeedbackSubmittedSession();
mHelper.mergeUnsyncedActions(withNoLocalUserActions());
assertThatMergeFeedbackSubmittedSessionsHas(REMOTE_SESSION_ID);
}
@Test
public void mergeUnsyncedActions_localAndRemoteFeedbackSubmittedSessions() {
withRemoteFeedbackSubmittedSession();
mHelper.mergeUnsyncedActions(withLocalFeedbackSubmittedActions());
assertThatMergeFeedbackSubmittedSessionsHas(REMOTE_SESSION_ID);
assertThatMergeFeedbackSubmittedSessionsHas(LOCAL_SESSION_ID);
}
@Test
public void getPendingFirebaseUpdatesMap_storesMergedGcmKey() {
withMergedGCMKey();
assertThat(mHelper.getPendingFirebaseUpdatesMap().values(),
hasItem(REMOTE_GCM_KEY));
}
@Test
public void getPendingFirebaseUpdatesMap_MergedDataHasSession() {
withMergedStarredSession(SESSION_ONE_ID);
assertThatSessionIsInSchedule(SESSION_ONE_ID, true);
assertThatTimestampIsStored(SESSION_ONE_ID);
}
@Test
public void getPendingFirebaseUpdatesMap_mergedDataHasSession_remoteDataHasSameSession() {
withMergedStarredSession(SESSION_ONE_ID);
withRemoteStarredSession(SESSION_ONE_ID);
assertThatTimestampIsStored(SESSION_ONE_ID);
assertThatSessionIsInSchedule(SESSION_ONE_ID, true);
}
@Test
public void getPendingFirebaseUpdatesMap_storesMergedViewedVideo() {
withMergedViewedVideos();
assertThat(mHelper.getPendingFirebaseUpdatesMap().keySet(),
hasItem(FirebaseUtils.getViewedVideoChildPath(UID, REMOTE_VIDEO_ID)));
}
@Test
public void getPendingFirebaseUpdatesMap_storesMergedFeedbackSubmittedSessions() {
withMergedFeedbackSubmittedSessions();
assertThat(mHelper.getPendingFirebaseUpdatesMap().keySet(),
hasItem(FirebaseUtils.getFeedbackSubmittedSessionChildPath(UID, LOCAL_SESSION_ID)));
assertThat(mHelper.getPendingFirebaseUpdatesMap().keySet(),
hasItem(FirebaseUtils.getFeedbackSubmittedSessionChildPath(UID,
REMOTE_SESSION_ID)));
}
@Test
public void buildPendingFirebaseUpdatesMap_storesLastActivityTimestamp() {
assertThat(mHelper.getPendingFirebaseUpdatesMap().keySet(),
hasItem(FirebaseUtils.getLastActivityTimestampChildPath(UID)));
}
/**
* Adds a GCM key to the local user data.
*/
private void withLocalGCMKey() {
mHelper.getLocalUserData().setGcmKey(LOCAL_GCM_KEY);
}
/**
* Adds a GCM key to the remote user data.
*/
private void withRemoteGCMKey() {
mHelper.getRemoteUserData().setGcmKey(REMOTE_GCM_KEY);
}
/**
* Adds a starred session to the remote user data.
*/
private void withRemoteStarredSession(String sessionId) {
mHelper.getRemoteUserData().getStarredSessions().put(sessionId,
new UserData.StarredSession(true,TIMESTAMP));
}
/**
* Creates and returns a {@link UserAction} list with a single local starred session action that
* requires sync.
*
* @param sessionId THe ID of the starred session.
*/
private List<UserAction> withLocalStarredSessionActions(final String sessionId) {
return new ArrayList<UserAction>() {{
add(createAddStarAction(sessionId, TIMESTAMP, true));
}};
}
/**
* Creates and returns a {@link UserAction} list with a single local starred session action that
* does not require sync.
*/
private List<UserAction> withLocalStarredSessionActionsNoSync() {
return new ArrayList<UserAction>() {{
add(createAddStarAction(LOCAL_SESSION_ID, TIMESTAMP, false));
}};
}
/**
* Adds a single starred session to remote user data and creates and returns a {@link
* UserAction} list with the same starred session. Ensures that the timestamp of the local
* action is greater than the timestamp of the remote starred session.
*/
private List<UserAction> withLocalRemoveRemoteAddRemoteStale() {
mHelper.getRemoteUserData().getStarredSessions().put(SESSION_ONE_ID,
new UserData.StarredSession(true, STALE_TIMESTAMP));
return new ArrayList<UserAction>() {{
add(createRemoveStarAction(SESSION_ONE_ID, CURRENT_TIMESTAMP, true));
}};
}
/**
* Adds a single starred session to remote user data and creates and returns a {@link
* UserAction} list with the same starred session. Ensures that the timestamp of the remote
* starred session is greater than the timestamp of the local action.
*/
private List<UserAction> withLocalRemoveRemoteAddLocalStale() {
mHelper.getRemoteUserData().getStarredSessions().put(SESSION_ONE_ID,
new UserData.StarredSession(true, CURRENT_TIMESTAMP));
return new ArrayList<UserAction>() {{
add(createRemoveStarAction(SESSION_ONE_ID, STALE_TIMESTAMP, true));
}};
}
/**
* Returns a list with no {@link UserAction} objects.
*/
private List<UserAction> withNoLocalUserActions() {
return new ArrayList<>();
}
/**
* Creates and returns a {@link UserAction} list which contains a single viewed video action
* that requires sync.
*/
private List<UserAction> withViewedVideoLocalActions() {
return new ArrayList<UserAction>() {{
add(createViewedVideoAction(LOCAL_VIDEO_ID, true));
}};
}
/**
* Creates and returns a {@link UserAction} list which contains a single viewed video action
* that *does not require* sync.
*/
private List<UserAction> withViewedVideoLocalActionsNoSync() {
return new ArrayList<UserAction>() {{
add(createViewedVideoAction(LOCAL_VIDEO_ID, false));
}};
}
/**
* Adds a starred session to merged user data.
*
* @param sessionId The Id of the starred session.
*/
private void withMergedStarredSession(String sessionId) {
mHelper.getMergedUserData().getStarredSessions().put(sessionId,
new UserData.StarredSession(true, CURRENT_TIMESTAMP));
}
/**
* Adds a remote viewed video to the remote user data.
*/
private void withRemoteViewedVideo() {
mHelper.getRemoteUserData().getViewedVideoIds().add(REMOTE_VIDEO_ID);
}
/**
* Adds a remote feedback submitted session.
*/
private void withRemoteFeedbackSubmittedSession() {
mHelper.getRemoteUserData().getFeedbackSubmittedSessionIds().add(REMOTE_SESSION_ID);
}
/**
* Creates and returns a {@link UserAction} list which contains a single feedback submitted
* session that requires sync.
*/
private List<UserAction> withLocalFeedbackSubmittedActions() {
return new ArrayList<UserAction>() {{
add(createFeedbackSubmittedSessionAction(LOCAL_SESSION_ID, true));
}};
}
/**
* Adds a GCM Key to the merged user data.
*/
private void withMergedGCMKey() {
mHelper.getMergedUserData().setGcmKey(REMOTE_GCM_KEY);
}
/**
* Adds viewed video IDs to merged user data.
*/
private void withMergedViewedVideos() {
mHelper.getMergedUserData().getViewedVideoIds().add(REMOTE_VIDEO_ID);
}
/**
* Adds feedback submitted session IDs to merged user data.
*/
private void withMergedFeedbackSubmittedSessions() {
mHelper.getMergedUserData().getFeedbackSubmittedSessionIds().add(LOCAL_SESSION_ID);
mHelper.getMergedUserData().getFeedbackSubmittedSessionIds().add(REMOTE_SESSION_ID);
}
/**
* Asserts that {@code gcmKey} is stored in merged user data.
*/
private void assertThatMergedGCMKeyIs(String gcmKey) {
assertThat(gcmKey, is(equalTo(mHelper.getMergedUserData().getGcmKey())));
}
/**
* Asserts that {@code sessionId} is stored in merged user data.
*
* @param sessionId The ID of themerged session.
*/
private void assertThatMergedStarSessionsHas(String sessionId) {
assertThat(mHelper.getMergedUserData().getStarredSessions().keySet(),
hasItem(sessionId));
}
/**
* Asserts that no session IDs are stored in merged user data.
*/
private void assertThatMergedStarSessionsHasNoSessions() {
assertThat(mHelper.getMergedUserData().getStarredSessions().keySet(),
is(Collections.<String>emptySet()));
}
/**
* Asserts whether {@code sessionId} is in schedule or not in merged user data.
*
* @param sessionId The ID of the starred session.
* @param inSchedule Tracks whether a session is in schedule or not.
*/
private void assertThatSessionIsInSchedule(String sessionId, boolean inSchedule) {
String mergedInScheduleKey =
FirebaseUtils.getStarredSessionInScheduleChildPath(UID, sessionId);
assertThat((boolean) mHelper.getPendingFirebaseUpdatesMap().get(mergedInScheduleKey),
is(inSchedule));
}
/**
* Asserts that the timestamp for {@code sessionId} is stored.
*
* @param sessionId The ID of the starred session.
*/
private void assertThatTimestampIsStored(String sessionId) {
String timestampKey = FirebaseUtils.getStarredSessionTimestampChildPath(UID, sessionId);
assertThat(mHelper.getPendingFirebaseUpdatesMap().keySet(), hasItem(timestampKey));
}
/**
* Asserts that {@code videoId} is stored in merged viewed videos user data.
*
* @param videoId The Id of the viewed video.
*/
private void assertThatMergedViewedVideosHas(String videoId) {
assertThat(mHelper.getMergedUserData().getViewedVideoIds(), hasItem(videoId));
}
/**
* Asserts that no video ID was stored in merged user data.
*/
private void assertThatMergedUserDataHasNoVideoIds() {
assertThat(mHelper.getMergedUserData().getViewedVideoIds(),
is(Collections.<String>emptySet()));
}
/**
* Asserts that {@code sessionId} is stored in merged feedback submitted sessions user data.
*
* @param sessionId The ID of the feedback submitted session.
*/
private void assertThatMergeFeedbackSubmittedSessionsHas(String sessionId) {
assertThat(mHelper.getMergedUserData().getFeedbackSubmittedSessionIds(),
hasItem(sessionId));
}
}