blob: dd3439f60b179514cf53ac7489e2278c599b4f09 [file] [log] [blame]
// Copyright 2019 The Feed Authors.
//
// 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.android.libraries.feed.feedsessionmanager;
import static com.google.android.libraries.feed.internalapi.store.Store.HEAD_SESSION_ID;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
import com.google.android.libraries.feed.api.common.ActionPropertiesWithId;
import com.google.android.libraries.feed.api.common.MutationContext;
import com.google.android.libraries.feed.api.common.SemanticPropertiesWithId;
import com.google.android.libraries.feed.api.common.testing.ContentIdGenerators;
import com.google.android.libraries.feed.api.common.testing.InternalProtocolBuilder;
import com.google.android.libraries.feed.api.modelprovider.ModelCursor;
import com.google.android.libraries.feed.api.modelprovider.ModelError;
import com.google.android.libraries.feed.api.modelprovider.ModelError.ErrorType;
import com.google.android.libraries.feed.api.modelprovider.ModelProvider;
import com.google.android.libraries.feed.api.modelprovider.ModelProviderFactory;
import com.google.android.libraries.feed.common.Result;
import com.google.android.libraries.feed.common.concurrent.testing.FakeMainThreadRunner;
import com.google.android.libraries.feed.common.concurrent.testing.FakeTaskQueue;
import com.google.android.libraries.feed.common.concurrent.testing.FakeThreadUtils;
import com.google.android.libraries.feed.common.functional.Consumer;
import com.google.android.libraries.feed.common.intern.Interner;
import com.google.android.libraries.feed.common.time.TimingUtils;
import com.google.android.libraries.feed.common.time.testing.FakeClock;
import com.google.android.libraries.feed.feedapplifecyclelistener.FeedAppLifecycleListener;
import com.google.android.libraries.feed.feedapplifecyclelistener.FeedLifecycleListener.LifecycleEvent;
import com.google.android.libraries.feed.feedmodelprovider.FeedModelProviderFactory;
import com.google.android.libraries.feed.feedsessionmanager.FeedSessionManager.SessionMutationTracker;
import com.google.android.libraries.feed.feedsessionmanager.FeedSessionManager.StreamSharedStateInterner;
import com.google.android.libraries.feed.feedsessionmanager.internal.HeadSessionImpl;
import com.google.android.libraries.feed.feedsessionmanager.internal.Session;
import com.google.android.libraries.feed.feedsessionmanager.internal.SessionCache;
import com.google.android.libraries.feed.host.config.Configuration;
import com.google.android.libraries.feed.host.config.Configuration.ConfigKey;
import com.google.android.libraries.feed.host.logging.RequestReason;
import com.google.android.libraries.feed.host.scheduler.SchedulerApi;
import com.google.android.libraries.feed.host.scheduler.SchedulerApi.RequestBehavior;
import com.google.android.libraries.feed.host.scheduler.SchedulerApi.SessionManagerState;
import com.google.android.libraries.feed.testing.protocoladapter.FakeProtocolAdapter;
import com.google.android.libraries.feed.testing.requestmanager.FakeActionUploadRequestManager;
import com.google.android.libraries.feed.testing.requestmanager.FakeRequestManager;
import com.google.android.libraries.feed.testing.store.FakeStore;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.protobuf.ByteString;
import com.google.search.now.feed.client.StreamDataProto.StreamDataOperation;
import com.google.search.now.feed.client.StreamDataProto.StreamPayload;
import com.google.search.now.feed.client.StreamDataProto.StreamSharedState;
import com.google.search.now.feed.client.StreamDataProto.StreamStructure;
import com.google.search.now.feed.client.StreamDataProto.StreamStructure.Operation;
import com.google.search.now.feed.client.StreamDataProto.StreamToken;
import com.google.search.now.feed.client.StreamDataProto.StreamUploadableAction;
import com.google.search.now.feed.client.StreamDataProto.UiContext;
import com.google.search.now.ui.piet.PietProto.PietSharedState;
import com.google.search.now.ui.piet.PietProto.Stylesheet;
import com.google.search.now.ui.piet.PietProto.Template;
import com.google.search.now.wire.feed.ConsistencyTokenProto.ConsistencyToken;
import com.google.search.now.wire.feed.ContentIdProto.ContentId;
import com.google.search.now.wire.feed.OpaqueActionDataForTestProto.OpaqueActionDataForTest;
import com.google.search.now.wire.feed.OpaqueActionDataProto.OpaqueActionData;
import com.google.search.now.wire.feed.PietSharedStateItemProto.PietSharedStateItem;
import com.google.search.now.wire.feed.ResponseProto.Response;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.robolectric.RobolectricTestRunner;
/** Tests of the {@link FeedSessionManager} class. */
@RunWith(RobolectricTestRunner.class)
public class FeedSessionManagerTest {
private static final MutationContext EMPTY_MUTATION = new MutationContext.Builder().build();
private static final ContentId SHARED_STATE_ID =
ContentId.newBuilder()
.setContentDomain("piet-shared-state")
.setId(1)
.setTable("piet-shared-state")
.build();
private static final String SESSION_ID = "session:1";
private final Configuration configuration =
new Configuration.Builder().put(ConfigKey.UNDOABLE_ACTIONS_ENABLED, true).build();
private final ContentIdGenerators contentIdGenerators = new ContentIdGenerators();
private final ContentIdGenerators idGenerators = new ContentIdGenerators();
private final FakeClock fakeClock = new FakeClock();
private final String rootContentId = idGenerators.createRootContentId(0);
private final TimingUtils timingUtils = new TimingUtils();
private FakeActionUploadRequestManager fakeActionUploadRequestManager;
private FakeMainThreadRunner fakeMainThreadRunner;
private FakeProtocolAdapter fakeProtocolAdapter;
private FakeRequestManager fakeRequestManager;
private FakeStore fakeStore;
private FakeTaskQueue fakeTaskQueue;
private FakeThreadUtils fakeThreadUtils;
private FeedAppLifecycleListener appLifecycleListener;
@Mock private SchedulerApi schedulerApi;
@Before
public void setUp() {
initMocks(this);
fakeThreadUtils = FakeThreadUtils.withThreadChecks();
fakeMainThreadRunner =
FakeMainThreadRunner.runTasksImmediatelyWithThreadChecks(fakeThreadUtils);
fakeTaskQueue = new FakeTaskQueue(MoreExecutors.directExecutor(), fakeClock, fakeThreadUtils);
appLifecycleListener = new FeedAppLifecycleListener(fakeThreadUtils);
fakeActionUploadRequestManager = new FakeActionUploadRequestManager(fakeThreadUtils);
fakeStore = new FakeStore(fakeThreadUtils, fakeTaskQueue, fakeClock);
fakeProtocolAdapter = new FakeProtocolAdapter();
fakeRequestManager =
new FakeRequestManager(
fakeThreadUtils, fakeMainThreadRunner, fakeProtocolAdapter, fakeTaskQueue);
fakeRequestManager.queueResponse(Response.getDefaultInstance());
when(schedulerApi.shouldSessionRequestData(any(SessionManagerState.class)))
.thenReturn(RequestBehavior.NO_REQUEST_WITH_CONTENT);
}
@Test
public void testInitialization() {
StreamSharedState sharedState =
StreamSharedState.newBuilder()
.setContentId(idGenerators.createFeatureContentId(0))
.setPietSharedStateItem(PietSharedStateItem.getDefaultInstance())
.build();
StreamStructure operation =
StreamStructure.newBuilder()
.setContentId(idGenerators.createFeatureContentId(0))
.setOperation(StreamStructure.Operation.UPDATE_OR_APPEND)
.build();
fakeStore.setSharedStates(sharedState).setStreamStructures(HEAD_SESSION_ID, operation);
FeedSessionManager sessionManager =
new FeedSessionManagerFactory(
fakeTaskQueue,
fakeStore,
timingUtils,
fakeThreadUtils,
fakeProtocolAdapter,
fakeRequestManager,
fakeActionUploadRequestManager,
schedulerApi,
configuration,
fakeClock,
appLifecycleListener)
.create();
assertThat(sessionManager.initialized.get()).isFalse();
sessionManager.initialize();
assertThat(sessionManager.initialized.get()).isTrue();
Map<String, StreamSharedState> sharedStateCache = sessionManager.getSharedStateCacheForTest();
assertThat(sharedStateCache).hasSize(1);
SessionCache sessionCache = sessionManager.getSessionCacheForTest();
Session head = sessionCache.getHead();
assertThat(head).isInstanceOf(HeadSessionImpl.class);
String itemKey = idGenerators.createFeatureContentId(0);
Set<String> content = head.getContentInSession();
assertThat(content).contains(itemKey);
assertThat(content).hasSize(1);
}
// This is testing a condition similar to the one that caused [INTERNAL LINK].
@Test
public void testInitialization_equalSharedStatesDifferentContentIds() throws Exception {
StreamSharedState sharedState1 =
StreamSharedState.newBuilder()
.setContentId("shared-state-1")
.setPietSharedStateItem(
PietSharedStateItem.newBuilder()
.setPietSharedState(
PietSharedState.newBuilder()
.addStylesheets(
Stylesheet.newBuilder().setStylesheetId("shared-stylesheet"))))
.build();
StreamSharedState sharedState2 =
StreamSharedState.newBuilder()
.setContentId("shared-state-2") // Different ContentId
.setPietSharedStateItem( // Equal PietSharedStateItem
PietSharedStateItem.parseFrom(sharedState1.getPietSharedStateItem().toByteString()))
.build();
assertThat(sharedState1).isNotEqualTo(sharedState2);
// Initial PietSharedStateItem messages are equal but not the same between the 2 shared states.
assertThat(sharedState1.getPietSharedStateItem())
.isEqualTo(sharedState2.getPietSharedStateItem());
assertThat(sharedState1.getPietSharedStateItem())
.isNotSameAs(sharedState2.getPietSharedStateItem());
StreamStructure operation =
StreamStructure.newBuilder()
.setContentId(idGenerators.createFeatureContentId(0))
.setOperation(StreamStructure.Operation.UPDATE_OR_APPEND)
.build();
fakeStore
.setSharedStates(sharedState1, sharedState2)
.setStreamStructures(HEAD_SESSION_ID, operation);
ContentId contentId1 = SHARED_STATE_ID.toBuilder().setId(1).build();
ContentId contentId2 = SHARED_STATE_ID.toBuilder().setId(2).build();
fakeProtocolAdapter
.addContentId("shared-state-1", contentId1)
.addContentId("shared-state-2", contentId2);
FeedSessionManager sessionManager =
new FeedSessionManagerFactory(
fakeTaskQueue,
fakeStore,
timingUtils,
fakeThreadUtils,
fakeProtocolAdapter,
fakeRequestManager,
fakeActionUploadRequestManager,
schedulerApi,
configuration,
fakeClock,
appLifecycleListener)
.create();
assertThat(sessionManager.initialized.get()).isFalse();
sessionManager.initialize();
assertThat(sessionManager.initialized.get()).isTrue();
Map<String, StreamSharedState> sharedStateCache = sessionManager.getSharedStateCacheForTest();
assertThat(sharedStateCache).hasSize(2);
StreamSharedState cachedSharedState1 = sessionManager.getSharedState(contentId1);
StreamSharedState cachedSharedState2 = sessionManager.getSharedState(contentId2);
assertThat(cachedSharedState1).isEqualTo(sharedState1);
assertThat(cachedSharedState2).isEqualTo(sharedState2);
// Cached PietSharedStateItem messages the same between the 2 shared states (memoized).
assertThat(cachedSharedState1.getPietSharedStateItem())
.isSameAs(cachedSharedState2.getPietSharedStateItem());
}
@Test
public void testLifecycleInitialization() {
FeedSessionManager sessionManager =
new FeedSessionManagerFactory(
fakeTaskQueue,
fakeStore,
timingUtils,
fakeThreadUtils,
fakeProtocolAdapter,
fakeRequestManager,
fakeActionUploadRequestManager,
schedulerApi,
configuration,
fakeClock,
appLifecycleListener)
.create();
assertThat(sessionManager.initialized.get()).isFalse();
sessionManager.onLifecycleEvent(LifecycleEvent.INITIALIZE);
assertThat(sessionManager.initialized.get()).isTrue();
sessionManager.onLifecycleEvent(LifecycleEvent.INITIALIZE);
assertThat(sessionManager.initialized.get()).isTrue();
}
@Test
public void testSessionWithContent() {
FeedSessionManager sessionManager = getInitializedSessionManager();
int featureCnt = 3;
populateSession(sessionManager, featureCnt, 1, true, null);
ModelProvider modelProvider = getModelProvider(sessionManager);
assertThat(modelProvider).isNotNull();
assertThat(modelProvider.getRootFeature()).isNotNull();
ModelCursor cursor = modelProvider.getRootFeature().getCursor();
int cursorCount = 0;
while (cursor.getNextItem() != null) {
cursorCount++;
}
assertThat(cursorCount).isEqualTo(featureCnt);
// append a couple of others
populateSession(sessionManager, featureCnt, featureCnt + 1, false, null);
cursor = modelProvider.getRootFeature().getCursor();
cursorCount = 0;
while (cursor.getNextItem() != null) {
cursorCount++;
}
assertThat(cursorCount).isEqualTo(featureCnt * 2);
}
@Test
public void testNoRequestWithContent_populateIsUserFacing() {
when(schedulerApi.shouldSessionRequestData(any(SessionManagerState.class)))
.thenReturn(RequestBehavior.NO_REQUEST_WITH_CONTENT);
FeedSessionManager sessionManager = getInitializedSessionManager();
populateSession(sessionManager, 3, 1, true, null);
fakeTaskQueue.resetCounts();
// Population will happen in a user-facing task and no request is sent.
ModelProvider modelProvider = getModelProvider(sessionManager);
assertThat(modelProvider).isNotNull();
assertThat(fakeTaskQueue.getImmediateTaskCount()).isEqualTo(0);
assertThat(fakeTaskQueue.getBackgroundTaskCount()).isEqualTo(0);
assertThat(fakeTaskQueue.getUserFacingTaskCount()).isEqualTo(1);
assertThat(fakeTaskQueue.isMakingRequest()).isFalse();
}
@Test
public void testRequestWithContent_populateIsImmediate() {
when(schedulerApi.shouldSessionRequestData(any(SessionManagerState.class)))
.thenReturn(RequestBehavior.REQUEST_WITH_CONTENT);
FeedSessionManager sessionManager = getInitializedSessionManager();
populateSession(sessionManager, 3, 1, true, null);
fakeTaskQueue.resetCounts();
// Population will happen immediately and a request is sent.
ModelProvider modelProvider = getModelProvider(sessionManager);
assertThat(modelProvider).isNotNull();
assertThat(fakeTaskQueue.getImmediateTaskCount()).isEqualTo(1);
assertThat(fakeTaskQueue.getBackgroundTaskCount()).isEqualTo(0);
assertThat(fakeTaskQueue.getUserFacingTaskCount()).isEqualTo(1);
assertThat(fakeTaskQueue.isMakingRequest()).isTrue();
}
@Test
public void testRequestWithWait_populateIsUserFacing() {
when(schedulerApi.shouldSessionRequestData(any(SessionManagerState.class)))
.thenReturn(RequestBehavior.REQUEST_WITH_WAIT);
FeedSessionManager sessionManager = getInitializedSessionManager();
populateSession(sessionManager, 3, 1, true, null);
fakeTaskQueue.resetCounts();
// Population will happen in a user-facing task and a request is sent.
ModelProvider modelProvider = getModelProvider(sessionManager);
assertThat(modelProvider).isNotNull();
assertThat(fakeTaskQueue.getImmediateTaskCount()).isEqualTo(0);
assertThat(fakeTaskQueue.getBackgroundTaskCount()).isEqualTo(0);
assertThat(fakeTaskQueue.getUserFacingTaskCount()).isEqualTo(2);
assertThat(fakeTaskQueue.isMakingRequest()).isTrue();
}
@Test
public void testNoCardsError() {
FeedSessionManager sessionManager = getInitializedSessionManager();
sessionManager.noCardsError = new ModelError(ErrorType.NO_CARDS_ERROR, null);
int featureCnt = 2;
populateSession(sessionManager, featureCnt, 1, true, null);
// We can't verify that raiseError was called on ModelProvider since all of that happens in
// the createNew() call due to the single threaded nature. We may want to have a test method
// on the factory to install the observer when creating the ModelProvider.
ModelProvider modelProvider = getModelProvider(sessionManager);
// Verify the failed session is correct
SessionCache sessionCache = sessionManager.getSessionCacheForTest();
assertThat(sessionCache.getAttachedSessions()).hasSize(1);
Session session = sessionCache.getAttached(modelProvider.getSessionId());
assertThat(session).isNotNull();
}
@Test
public void testModelErrorObserver() {
FeedSessionManager sessionManager = getInitializedSessionManager();
// verify this runs. Another method that can'be be verified on a single thread since
// the noCardsError will be set and unset.
sessionManager.modelErrorObserver(null, new ModelError(ErrorType.NO_CARDS_ERROR, null));
}
@Test
public void testReset() {
FeedSessionManager sessionManager = getInitializedSessionManager();
int featureCnt = 3;
int fullFeatureCount = populateSession(sessionManager, featureCnt, 1, true, null);
assertThat(fullFeatureCount).isEqualTo(featureCnt + 1);
fullFeatureCount = populateSession(sessionManager, featureCnt, 1, true, null);
assertThat(fullFeatureCount).isEqualTo(featureCnt + 1);
}
@Test
public void testHandleToken() {
ByteString bytes = ByteString.copyFrom("continuation", Charset.defaultCharset());
StreamToken streamToken =
StreamToken.newBuilder().setNextPageToken(bytes).setParentId(rootContentId).build();
FeedSessionManager sessionManager = getInitializedSessionManager();
sessionManager.handleToken(SESSION_ID, streamToken);
assertThat(fakeRequestManager.getLatestStreamToken()).isEqualTo(streamToken);
}
@Test
public void testForceRefresh() {
FeedSessionManager sessionManager = getInitializedSessionManager();
sessionManager.triggerRefresh(
SESSION_ID, RequestReason.ZERO_STATE, UiContext.getDefaultInstance());
assertThat(fakeRequestManager.getLatestRequestReason()).isEqualTo(RequestReason.ZERO_STATE);
}
@Test
public void testForceRefresh_scheduledRefresh() {
FeedSessionManager sessionManager = getInitializedSessionManager();
sessionManager.triggerRefresh(
SESSION_ID, RequestReason.HOST_REQUESTED, UiContext.getDefaultInstance());
assertThat(fakeRequestManager.getLatestRequestReason()).isEqualTo(RequestReason.HOST_REQUESTED);
}
@Test
public void testGetSharedState() {
FeedSessionManager sessionManager = getInitializedSessionManager();
String sharedStateId = idGenerators.createSharedStateContentId(0);
ContentId undefinedSharedStateId =
ContentId.newBuilder()
.setContentDomain("shared-state")
.setId(5)
.setTable("shared-states")
.build();
String undefinedStreamSharedStateId =
idGenerators.createSharedStateContentId(undefinedSharedStateId.getId());
fakeProtocolAdapter
.addContentId(sharedStateId, SHARED_STATE_ID)
.addContentId(undefinedStreamSharedStateId, undefinedSharedStateId);
populateSession(sessionManager, 3, 1, true, sharedStateId);
assertThat(sessionManager.getSharedState(SHARED_STATE_ID)).isNotNull();
// test the null condition
assertThat(sessionManager.getSharedState(undefinedSharedStateId)).isNull();
}
@Test
public void testUpdateConsumer() {
FeedSessionManager sessionManager = getInitializedSessionManager();
assertThat(sessionManager.outstandingMutations).isEmpty();
Consumer<Result<List<StreamDataOperation>>> updateConsumer =
sessionManager.getUpdateConsumer(EMPTY_MUTATION);
assertThat(updateConsumer).isInstanceOf(SessionMutationTracker.class);
assertThat(sessionManager.outstandingMutations).hasSize(1);
assertThat(sessionManager.outstandingMutations).contains(updateConsumer);
updateConsumer.accept(Result.success(new ArrayList<>()));
assertThat(sessionManager.outstandingMutations).isEmpty();
}
@Test
public void testUpdateConsumer_clearAll() {
FeedSessionManager sessionManager = getInitializedSessionManager();
assertThat(sessionManager.outstandingMutations).isEmpty();
Consumer<Result<List<StreamDataOperation>>> updateConsumer =
sessionManager.getUpdateConsumer(EMPTY_MUTATION);
assertThat(sessionManager.outstandingMutations).hasSize(1);
appLifecycleListener.onClearAll();
assertThat(sessionManager.outstandingMutations).isEmpty();
// verify this still runs (as a noop)
updateConsumer.accept(Result.success(new ArrayList<>()));
assertThat(sessionManager.outstandingMutations).isEmpty();
}
@Test
public void testUpdateConsumer_clearAllWithRefresh() {
FeedSessionManager sessionManager = getInitializedSessionManager();
assertThat(sessionManager.outstandingMutations).isEmpty();
Consumer<Result<List<StreamDataOperation>>> updateConsumer =
sessionManager.getUpdateConsumer(EMPTY_MUTATION);
assertThat(sessionManager.outstandingMutations).hasSize(1);
appLifecycleListener.onClearAllWithRefresh();
assertThat(sessionManager.outstandingMutations).isEmpty();
// verify this still runs (as a noop)
updateConsumer.accept(Result.success(new ArrayList<>()));
assertThat(sessionManager.outstandingMutations).isEmpty();
}
@Test
public void testEdit_actionProperties() {
FeedSessionManager sessionManager =
new FeedSessionManagerFactory(
fakeTaskQueue,
fakeStore,
timingUtils,
fakeThreadUtils,
fakeProtocolAdapter,
fakeRequestManager,
fakeActionUploadRequestManager,
schedulerApi,
configuration,
fakeClock,
appLifecycleListener)
.create();
sessionManager.initialize();
OpaqueActionData actionData =
OpaqueActionData.newBuilder()
.setExtension(
OpaqueActionDataForTest.opaqueActionDataForTestExtension,
OpaqueActionDataForTest.newBuilder().setId("id").build())
.build();
StreamDataOperation streamDataOperation =
StreamDataOperation.newBuilder()
.setStreamPayload(StreamPayload.newBuilder().setActionData(actionData))
.setStreamStructure(
StreamStructure.newBuilder()
.setContentId(rootContentId)
.setOperation(Operation.UPDATE_OR_APPEND))
.build();
Consumer<Result<List<StreamDataOperation>>> updateConsumer =
sessionManager.getUpdateConsumer(EMPTY_MUTATION);
Result<List<StreamDataOperation>> result = Result.success(listOf(streamDataOperation));
updateConsumer.accept(result);
assertThat(fakeStore.getContentById(rootContentId))
.contains(new ActionPropertiesWithId(rootContentId, actionData));
}
@Test
public void testEdit_semanticProperties() {
FeedSessionManager sessionManager =
new FeedSessionManagerFactory(
fakeTaskQueue,
fakeStore,
timingUtils,
fakeThreadUtils,
fakeProtocolAdapter,
fakeRequestManager,
fakeActionUploadRequestManager,
schedulerApi,
configuration,
fakeClock,
appLifecycleListener)
.create();
sessionManager.initialize();
ByteString semanticData = ByteString.copyFromUtf8("helloWorld");
StreamDataOperation streamDataOperation =
StreamDataOperation.newBuilder()
.setStreamPayload(StreamPayload.newBuilder().setSemanticData(semanticData))
.setStreamStructure(
StreamStructure.newBuilder()
.setContentId(rootContentId)
.setOperation(Operation.UPDATE_OR_APPEND))
.build();
Consumer<Result<List<StreamDataOperation>>> updateConsumer =
sessionManager.getUpdateConsumer(EMPTY_MUTATION);
Result<List<StreamDataOperation>> result = Result.success(listOf(streamDataOperation));
updateConsumer.accept(result);
assertThat(fakeStore.getContentById(rootContentId))
.contains(new SemanticPropertiesWithId(rootContentId, semanticData.toByteArray()));
}
@Test
public void testSwitchToEphemeralMode() {
FeedSessionManager sessionManager = getUninitializedSessionManager();
fakeThreadUtils.enforceMainThread(false);
sessionManager.switchToEphemeralMode("An Error Message");
assertThat(fakeStore.isEphemeralMode()).isTrue();
}
@Test
public void testOnSwitchToEphemeralMode() {
FeedSessionManager sessionManager = getInitializedSessionManager();
String sharedStateId = idGenerators.createSharedStateContentId(0);
int featureCount = 3;
populateSession(sessionManager, featureCount, 1, true, sharedStateId);
Map<String, StreamSharedState> sharedStates = sessionManager.getSharedStateCacheForTest();
assertThat(sharedStates).hasSize(1);
SessionCache sessionCache = sessionManager.getSessionCacheForTest();
assertThat(sessionCache.getAttachedSessions()).isEmpty();
Session session = sessionCache.getHead();
assertThat(session).isNotNull();
Set<String> contentInSession = session.getContentInSession();
assertThat(contentInSession).hasSize(featureCount + 1);
fakeThreadUtils.enforceMainThread(false);
sessionManager.onSwitchToEphemeralMode();
sharedStates = sessionManager.getSharedStateCacheForTest();
assertThat(sharedStates).hasSize(0);
assertThat(sessionCache.getAttachedSessions()).isEmpty();
session = sessionCache.getHead();
assertThat(session).isNotNull();
contentInSession = session.getContentInSession();
assertThat(contentInSession).hasSize(0);
}
@Test
public void testErrors_initializationSharedStateError() {
fakeStore.setAllowGetSharedStates(false);
FeedSessionManager sessionManager = getUninitializedSessionManager();
sessionManager.initialize();
assertThat(fakeStore.isEphemeralMode()).isTrue();
}
@Test
public void testErrors_initializationStreamStructureError() {
fakeStore.setAllowGetStreamStructures(false);
FeedSessionManager sessionManager = getUninitializedSessionManager();
sessionManager.initialize();
assertThat(fakeStore.isEphemeralMode()).isTrue();
}
@Test
public void testErrors_createNewSessionError() {
fakeStore.setAllowCreateNewSession(false);
FeedSessionManager sessionManager = getUninitializedSessionManager();
sessionManager.initialize();
populateSession(sessionManager, 5, 1, true, null);
ModelProvider unused = getModelProvider(sessionManager);
assertThat(fakeStore.isEphemeralMode()).isTrue();
}
@Test
public void testErrors_getStreamStructuresError() {
FeedSessionManager sessionManager = getUninitializedSessionManager();
sessionManager.initialize();
fakeStore.setAllowGetStreamStructures(false);
populateSession(sessionManager, 5, 1, true, null);
ModelProvider unused = getModelProvider(sessionManager);
assertThat(fakeStore.isEphemeralMode()).isTrue();
}
@Test
public void testTriggerUploadActions() {
FeedSessionManager sessionManager =
getInitializedSessionManager(
new Configuration.Builder().put(ConfigKey.UNDOABLE_ACTIONS_ENABLED, true).build());
HashSet<StreamUploadableAction> actionSet = new HashSet<>();
actionSet.add(StreamUploadableAction.getDefaultInstance());
ConsistencyToken token =
ConsistencyToken.newBuilder().setToken(ByteString.copyFrom(new byte[] {0x1, 0xf})).build();
fakeThreadUtils.enforceMainThread(false);
sessionManager.getConsistencyTokenConsumer().accept(Result.success(token));
sessionManager.triggerUploadActions(actionSet);
assertThat(fakeActionUploadRequestManager.getLatestActions())
.containsExactlyElementsIn(actionSet);
}
@Test
public void testGetConsistencyToken() {
FeedSessionManager sessionManager =
getInitializedSessionManager(
new Configuration.Builder().put(ConfigKey.UNDOABLE_ACTIONS_ENABLED, true).build());
ConsistencyToken token =
ConsistencyToken.newBuilder().setToken(ByteString.copyFrom(new byte[] {0x1, 0xf})).build();
fakeThreadUtils.enforceMainThread(false);
sessionManager.getConsistencyTokenConsumer().accept(Result.success(token));
assertThat(sessionManager.getConsistencyToken()).isEqualTo(token);
}
@Test
public void testGetConsistencyTokenEmpty() {
FeedSessionManager sessionManager = getInitializedSessionManager();
fakeThreadUtils.enforceMainThread(false);
assertThat(sessionManager.getConsistencyToken())
.isEqualTo(ConsistencyToken.getDefaultInstance());
}
@Test
public void testFetchActionsAndUpload() {
FeedSessionManager sessionManager = getInitializedSessionManager();
ConsistencyToken token =
ConsistencyToken.newBuilder().setToken(ByteString.copyFrom(new byte[] {0x1, 0xf})).build();
Consumer<Result<ConsistencyToken>> consumer =
result -> {
assertThat(result.isSuccessful()).isTrue();
assertThat(result.getValue()).isEqualTo(token);
};
fakeActionUploadRequestManager.setResult(Result.success(token));
fakeThreadUtils.enforceMainThread(false);
sessionManager.getConsistencyTokenConsumer().accept(Result.success(token));
sessionManager.fetchActionsAndUpload(consumer);
assertThat(fakeActionUploadRequestManager.getLatestActions()).isNotNull();
}
@Test
public void testStreamSharedStateInterner() {
Interner<StreamSharedState> interner = new StreamSharedStateInterner();
StreamSharedState first =
StreamSharedState.newBuilder()
.setContentId("foo")
.setPietSharedStateItem(
PietSharedStateItem.newBuilder()
.setPietSharedState(
PietSharedState.newBuilder()
.addTemplates(Template.newBuilder().setTemplateId("equal"))))
.build();
StreamSharedState second =
StreamSharedState.newBuilder()
.setContentId("baz")
.setPietSharedStateItem(
PietSharedStateItem.newBuilder()
.setPietSharedState(
PietSharedState.newBuilder()
.addTemplates(Template.newBuilder().setTemplateId("equal"))))
.build();
StreamSharedState third =
StreamSharedState.newBuilder()
.setContentId("bar")
.setPietSharedStateItem(
PietSharedStateItem.newBuilder()
.setPietSharedState(
PietSharedState.newBuilder()
.addTemplates(Template.newBuilder().setTemplateId("different"))))
.build();
assertThat(first).isNotSameAs(second);
assertThat(first.getPietSharedStateItem()).isEqualTo(second.getPietSharedStateItem());
assertThat(first).isNotEqualTo(third);
assertThat(first.getPietSharedStateItem()).isNotEqualTo(third.getPietSharedStateItem());
// Pool is empty so first is added/returned.
StreamSharedState internedFirst = interner.intern(first);
assertThat(interner.size()).isEqualTo(1);
assertThat(internedFirst).isSameAs(first);
// Pool already has an identical inner PietSharedStateItem proto, which is used.
StreamSharedState internedSecond = interner.intern(second);
assertThat(interner.size()).isEqualTo(1);
// The returned proto is equal to second, but its internal PietSharedStateItem is the same as
// the one in first (memoized).
assertThat(internedSecond).isNotSameAs(second);
assertThat(internedSecond).isEqualTo(second);
assertThat(internedSecond.getPietSharedStateItem()).isSameAs(first.getPietSharedStateItem());
// Third has a new PietSharedStateItem (not equal with any previous) so it is added to the pool.
StreamSharedState internedThird = interner.intern(third);
assertThat(interner.size()).isEqualTo(2);
assertThat(internedThird).isSameAs(third);
}
private int populateSession(
FeedSessionManager sessionManager,
int featureCnt,
int idStart,
boolean reset,
/*@Nullable*/ String sharedStateId) {
int operationCount = 0;
InternalProtocolBuilder internalProtocolBuilder = new InternalProtocolBuilder();
if (reset) {
internalProtocolBuilder.addClearOperation().addRootFeature();
operationCount++;
}
for (int i = 0; i < featureCnt; i++) {
internalProtocolBuilder.addFeature(
contentIdGenerators.createFeatureContentId(idStart++),
idGenerators.createRootContentId(0));
operationCount++;
}
if (sharedStateId != null) {
internalProtocolBuilder.addSharedState(sharedStateId);
operationCount++;
}
Consumer<Result<List<StreamDataOperation>>> updateConsumer =
sessionManager.getUpdateConsumer(EMPTY_MUTATION);
List<StreamDataOperation> operations = new ArrayList<>();
operations.addAll(internalProtocolBuilder.build());
Result<List<StreamDataOperation>> result = Result.success(operations);
updateConsumer.accept(result);
return operationCount;
}
private ModelProvider getModelProvider(FeedSessionManager sessionManager) {
ModelProviderFactory modelProviderFactory =
new FeedModelProviderFactory(
sessionManager,
fakeThreadUtils,
timingUtils,
fakeTaskQueue,
fakeMainThreadRunner,
configuration);
return modelProviderFactory.createNew(null);
}
private FeedSessionManager getInitializedSessionManager() {
return getInitializedSessionManager(configuration);
}
private FeedSessionManager getInitializedSessionManager(Configuration config) {
FeedSessionManager fsm =
new FeedSessionManagerFactory(
fakeTaskQueue,
fakeStore,
timingUtils,
fakeThreadUtils,
fakeProtocolAdapter,
fakeRequestManager,
fakeActionUploadRequestManager,
schedulerApi,
config,
fakeClock,
appLifecycleListener)
.create();
fsm.initialize();
return fsm;
}
private FeedSessionManager getUninitializedSessionManager() {
return new FeedSessionManagerFactory(
fakeTaskQueue,
fakeStore,
timingUtils,
fakeThreadUtils,
fakeProtocolAdapter,
fakeRequestManager,
fakeActionUploadRequestManager,
schedulerApi,
configuration,
fakeClock,
appLifecycleListener)
.create();
}
private static <T> List<T> listOf(T... items) {
ArrayList<T> result = new ArrayList<>(items.length);
Collections.addAll(result, items);
return result;
}
}