blob: 929d2a62a205b5f0914f1854009dbcd007660666 [file] [log] [blame]
// Copyright 2018 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.internal;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.when;
import com.google.android.libraries.feed.api.common.MutationContext;
import com.google.android.libraries.feed.api.internal.common.testing.InternalProtocolBuilder;
import com.google.android.libraries.feed.api.internal.modelprovider.ModelChild;
import com.google.android.libraries.feed.api.internal.modelprovider.ModelProvider.ViewDepthProvider;
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.time.TimingUtils;
import com.google.android.libraries.feed.common.time.testing.FakeClock;
import com.google.android.libraries.feed.feedsessionmanager.internal.testing.AbstractSessionImplTest;
import com.google.android.libraries.feed.testing.modelprovider.FakeModelChild;
import com.google.android.libraries.feed.testing.modelprovider.FakeModelCursor;
import com.google.android.libraries.feed.testing.modelprovider.FakeModelFeature;
import com.google.android.libraries.feed.testing.modelprovider.FakeModelMutation;
import com.google.android.libraries.feed.testing.modelprovider.FakeModelToken;
import com.google.search.now.feed.client.StreamDataProto.StreamStructure;
import com.google.search.now.feed.client.StreamDataProto.StreamToken;
import com.google.search.now.feed.client.StreamDataProto.UiContext;
import java.util.ArrayList;
import java.util.List;
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 TimeoutSessionImpl} class. */
@RunWith(RobolectricTestRunner.class)
public class TimeoutSessionImplTest extends AbstractSessionImplTest {
private final FakeClock fakeClock = new FakeClock();
private final FakeThreadUtils fakeThreadUtils = FakeThreadUtils.withThreadChecks();
private final TimingUtils timingUtils = new TimingUtils();
@Mock private ViewDepthProvider viewDepthProvider;
@Before
@Override
public void setUp() {
super.setUp();
fakeThreadUtils.enforceMainThread(false);
}
@Test
public void testInvalidateOnResetHead() {
TimeoutSessionImpl session = getSessionImpl();
assertThat(session.invalidateOnResetHead()).isFalse();
}
@Test
public void testClearHead() {
SessionImpl session = getSessionImpl();
session.setSessionId(TEST_SESSION_ID);
session.populateModelProvider(new ArrayList<>(), false, true, UiContext.getDefaultInstance());
int featureCnt = 3;
int featureId = 5;
InternalProtocolBuilder protocolBuilder = new InternalProtocolBuilder().addClearOperation();
addFeatures(protocolBuilder, featureCnt, featureId);
List<StreamStructure> streamStructures = protocolBuilder.buildAsStreamStructure();
// 1 clear, 3 features
assertThat(streamStructures).hasSize(4);
session.updateSession(true, streamStructures, null);
assertThat(fakeSessionMutation.streamStructures).hasSize(featureCnt);
FakeModelMutation fakeModelMutation = fakeModelProvider.getLatestModelMutation();
assertThat(fakeModelMutation.addedChildren).hasSize(featureCnt);
assertThat(session.getContentInSession()).hasSize(featureCnt);
assertThat(session.getContentInSession())
.contains(contentIdGenerators.createFeatureContentId(featureId));
}
@Test
public void testClearHead_onPaginationRequest() {
SessionImpl session = getSessionImpl();
session.setSessionId(TEST_SESSION_ID);
session.populateModelProvider(new ArrayList<>(), false, true, UiContext.getDefaultInstance());
int featureCnt = 3;
int featureId = 5;
InternalProtocolBuilder protocolBuilder = new InternalProtocolBuilder().addClearOperation();
addFeatures(protocolBuilder, featureCnt, featureId);
List<StreamStructure> streamStructures = protocolBuilder.buildAsStreamStructure();
session.bindModelProvider(fakeModelProvider, viewDepthProvider);
session.populateModelProvider(new ArrayList<>(), false, false, UiContext.getDefaultInstance());
session.updateSession(
true,
streamStructures,
new MutationContext.Builder()
.setRequestingSessionId(TEST_SESSION_ID)
.setContinuationToken(StreamToken.newBuilder().setContentId("token").build())
.build());
assertThat(fakeModelProvider.isInvalidated()).isTrue();
}
@Test
public void testPopulateModelProviderState() {
TimeoutSessionImpl session = getSessionImpl();
assertThat(session.legacyHeadContent).isFalse();
assertThat(session.viewDepthProvider).isNull();
session.setSessionId(TEST_SESSION_ID);
session.bindModelProvider(fakeModelProvider, viewDepthProvider);
session.populateModelProvider(new ArrayList<>(), false, true, UiContext.getDefaultInstance());
assertThat(session.legacyHeadContent).isTrue();
assertThat(session.viewDepthProvider).isEqualTo(viewDepthProvider);
}
@Test
public void testCreateRemoveFeature() {
TimeoutSessionImpl session = getSessionImpl();
String contentId = "contentId";
String parentContentId = "parentId";
StreamStructure streamStructure = session.createRemoveFeature(contentId, parentContentId);
assertThat(streamStructure.getContentId()).isEqualTo(contentId);
assertThat(streamStructure.getParentContentId()).isEqualTo(parentContentId);
}
@Test
public void testCreateRemoveFeature_nullParent() {
TimeoutSessionImpl session = getSessionImpl();
String contentId = "contentId";
StreamStructure streamStructure = session.createRemoveFeature(contentId, null);
assertThat(streamStructure.getContentId()).isEqualTo(contentId);
assertThat(streamStructure.getParentContentId()).isEmpty();
}
@Test
public void testCaptureRootContent() {
TimeoutSessionImpl session = getSessionImpl();
setupModelProviderRoot();
List<ModelChild> children = session.captureRootContent();
assertThat(children).isNotNull();
assertThat(children).hasSize(1);
}
@Test
public void testCaptureRootContent_nullRoot() {
TimeoutSessionImpl session = getSessionImpl();
List<ModelChild> children = session.captureRootContent();
assertThat(children).isEmpty();
}
@Test
public void testRemoveItems() {
TimeoutSessionImpl session = getSessionImpl();
List<ModelChild> modelChildren = new ArrayList<>();
for (int i = 0; i < 10; i++) {
modelChildren.add(createModelChild(i, ModelChild.Type.FEATURE));
}
setupForViewDepthProvider(
session, contentIdGenerators.createFeatureContentId(6), modelChildren);
// Items below the lowest child should be removed.
session.updateSession(/* clearHead= */ true, new ArrayList<>(), /* mutationContext= */ null);
FakeModelMutation fakeModelMutation = fakeModelProvider.getLatestModelMutation();
assertThat(fakeModelMutation.removedChildren).hasSize(3);
assertThat(fakeModelMutation.removedChildren.get(0).getContentId())
.isEqualTo(contentIdGenerators.createFeatureContentId(7));
assertThat(fakeModelMutation.removedChildren.get(1).getContentId())
.isEqualTo(contentIdGenerators.createFeatureContentId(8));
assertThat(fakeModelMutation.removedChildren.get(2).getContentId())
.isEqualTo(contentIdGenerators.createFeatureContentId(9));
}
@Test
public void testRemoveItems_visibleTokenIsRemoved() {
TimeoutSessionImpl session = getSessionImpl();
List<ModelChild> modelChildren = new ArrayList<>();
modelChildren.add(createModelChild(0, ModelChild.Type.TOKEN));
String tokenContentId = contentIdGenerators.createFeatureContentId(0);
setupForViewDepthProvider(session, tokenContentId, modelChildren);
// Because the lowest child is a token it should also be removed.
session.updateSession(/* clearHead= */ true, new ArrayList<>(), /* mutationContext= */ null);
FakeModelMutation fakeModelMutation = fakeModelProvider.getLatestModelMutation();
assertThat(fakeModelMutation.removedChildren).hasSize(1);
assertThat(fakeModelMutation.removedChildren.get(0).getContentId()).isEqualTo(tokenContentId);
}
@Test
public void testRemoveItems_nullLowestChildShouldRemoveToken() {
TimeoutSessionImpl session = getSessionImpl();
List<ModelChild> modelChildren = new ArrayList<>();
for (int i = 0; i < 10; i++) {
modelChildren.add(createModelChild(i, ModelChild.Type.FEATURE));
}
modelChildren.add(createModelChild(11, ModelChild.Type.TOKEN));
setupForViewDepthProvider(session, /* lowestChild= */ null, modelChildren);
// The token should be removed even when there is a null lowest child.
session.updateSession(/* clearHead= */ true, new ArrayList<>(), /* mutationContext= */ null);
FakeModelMutation fakeModelMutation = fakeModelProvider.getLatestModelMutation();
assertThat(fakeModelMutation.removedChildren).hasSize(1);
assertThat(fakeModelMutation.removedChildren.get(0).getContentId())
.isEqualTo(contentIdGenerators.createFeatureContentId(11));
}
private ModelChild createModelChild(long id, @ModelChild.Type int type) {
String contentId = contentIdGenerators.createFeatureContentId(id);
switch (type) {
case ModelChild.Type.FEATURE:
return FakeModelChild.newBuilder()
.setContentId(contentId)
.setModelFeature(FakeModelFeature.newBuilder().build())
.build();
case ModelChild.Type.TOKEN:
return FakeModelChild.newBuilder()
.setContentId(contentId)
.setModelToken(FakeModelToken.newBuilder().build())
.build();
default:
return FakeModelChild.newBuilder().setContentId(contentId).build();
}
}
private void setupModelProviderRoot() {
fakeModelProvider.triggerOnSessionStart(
FakeModelFeature.newBuilder()
.setModelCursor(
FakeModelCursor.newBuilder().addChild(FakeModelChild.newBuilder().build()).build())
.build());
}
@Override
protected TimeoutSessionImpl getSessionImpl() {
FakeTaskQueue fakeTaskQueue = new FakeTaskQueue(fakeClock, fakeThreadUtils);
fakeTaskQueue.initialize(() -> {});
TimeoutSessionImpl session =
new TimeoutSessionImpl(store, false, fakeTaskQueue, timingUtils, fakeThreadUtils);
session.bindModelProvider(fakeModelProvider, /* viewDepthProvider= */ null);
return session;
}
private void setupForViewDepthProvider(
TimeoutSessionImpl session, String lowestChild, List<ModelChild> modelChildren) {
when(viewDepthProvider.getChildViewDepth()).thenReturn(lowestChild);
fakeModelProvider.triggerOnSessionStart(
FakeModelFeature.newBuilder()
.setModelCursor(FakeModelCursor.newBuilder().addChildren(modelChildren).build())
.build());
session.setSessionId(TEST_SESSION_ID);
session.bindModelProvider(fakeModelProvider, viewDepthProvider);
session.populateModelProvider(
new ArrayList<>(),
/* cachedBindings= */ false,
/* legacyHeadContent= */ true,
UiContext.getDefaultInstance());
}
}