blob: 865e639f73f677894b8918f55ae3f577daccedf8 [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.internal;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.MockitoAnnotations.initMocks;
import com.google.android.libraries.feed.api.common.MutationContext;
import com.google.android.libraries.feed.api.internal.common.testing.ContentIdGenerators;
import com.google.android.libraries.feed.api.internal.common.testing.InternalProtocolBuilder;
import com.google.android.libraries.feed.api.internal.store.Store;
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.testing.store.FakeStore;
import com.google.search.now.feed.client.StreamDataProto.StreamStructure;
import com.google.search.now.feed.client.StreamDataProto.StreamToken;
import java.util.List;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
/** Tests of the {@link HeadSessionImpl} class. */
@RunWith(RobolectricTestRunner.class)
public class HeadSessionImplTest {
private final ContentIdGenerators contentIdGenerators = new ContentIdGenerators();
private final FakeClock fakeClock = new FakeClock();
private final FakeThreadUtils fakeThreadUtils = FakeThreadUtils.withoutThreadChecks();
private final TimingUtils timingUtils = new TimingUtils();
private FakeStore fakeStore;
@Before
public void setUp() {
initMocks(this);
fakeStore =
new FakeStore(fakeThreadUtils, new FakeTaskQueue(fakeClock, fakeThreadUtils), fakeClock);
}
@Test
public void testMinimalSessionManager() {
HeadSessionImpl headSession = new HeadSessionImpl(fakeStore, timingUtils);
assertThat(headSession.getSessionId()).isEqualTo(Store.HEAD_SESSION_ID);
}
@Test
public void testInvalidateOnResetHead() {
HeadSessionImpl session = new HeadSessionImpl(fakeStore, timingUtils);
assertThat(session.invalidateOnResetHead()).isFalse();
}
@Test
public void testUpdateSession_features() {
HeadSessionImpl headSession = new HeadSessionImpl(fakeStore, timingUtils);
int featureCnt = 3;
InternalProtocolBuilder protocolBuilder = new InternalProtocolBuilder().addClearOperation();
addFeatures(protocolBuilder, featureCnt, 1);
List<StreamStructure> streamStructures = protocolBuilder.buildAsStreamStructure();
// 1 clear, 3 features
assertThat(streamStructures).hasSize(featureCnt + 1);
headSession.updateSession(false, streamStructures, null);
// expect: 3 features
assertThat(headSession.getContentInSession()).hasSize(featureCnt);
assertThat(headSession.getContentInSession())
.contains(contentIdGenerators.createFeatureContentId(1));
assertThat(getContentInSession()).hasSize(featureCnt);
}
@Test
public void testReset() {
HeadSessionImpl headSession = new HeadSessionImpl(fakeStore, timingUtils);
int featureCnt = 5;
InternalProtocolBuilder protocolBuilder = new InternalProtocolBuilder().addClearOperation();
addFeatures(protocolBuilder, featureCnt, 1);
List<StreamStructure> streamStructures = protocolBuilder.buildAsStreamStructure();
assertThat(streamStructures).hasSize(featureCnt + 1);
headSession.updateSession(false, streamStructures, null);
assertThat(headSession.getContentInSession()).hasSize(featureCnt);
headSession.reset();
assertThat(headSession.getContentInSession()).isEmpty();
}
@Test
public void testUpdateSession_token() {
HeadSessionImpl headSession = new HeadSessionImpl(fakeStore, timingUtils);
int featureCnt = 3;
InternalProtocolBuilder protocolBuilder = new InternalProtocolBuilder().addClearOperation();
addFeatures(protocolBuilder, featureCnt, 1);
protocolBuilder.addToken(contentIdGenerators.createTokenContentId(1));
List<StreamStructure> streamStructures = protocolBuilder.buildAsStreamStructure();
// 1 clear, 3 features, token
assertThat(streamStructures).hasSize(5);
headSession.updateSession(false, streamStructures, null);
// expect: 3 features, 1 token
assertThat(headSession.getContentInSession()).hasSize(featureCnt + 1);
assertThat(headSession.getContentInSession())
.contains(contentIdGenerators.createFeatureContentId(1));
assertThat(getContentInSession()).hasSize(featureCnt + 1);
}
@Test
public void testUpdateFromToken() {
HeadSessionImpl headSession = new HeadSessionImpl(fakeStore, timingUtils);
int featureCnt = 3;
InternalProtocolBuilder protocolBuilder = new InternalProtocolBuilder();
addFeatures(protocolBuilder, featureCnt, 1);
List<StreamStructure> streamStructures = protocolBuilder.buildAsStreamStructure();
StreamToken token =
StreamToken.newBuilder().setContentId(contentIdGenerators.createTokenContentId(2)).build();
// The token needs to be in the session so update its content IDs with the token.
List<StreamStructure> tokenStructures =
new InternalProtocolBuilder().addToken(token.getContentId()).buildAsStreamStructure();
headSession.updateSession(false, tokenStructures, null);
MutationContext context = new MutationContext.Builder().setContinuationToken(token).build();
headSession.updateSession(false, streamStructures, context);
// features 3, plus the token added above
assertThat(headSession.getContentInSession()).hasSize(featureCnt + 1);
}
@Test
public void testUpdateFromToken_notInSession() {
HeadSessionImpl headSession = new HeadSessionImpl(fakeStore, timingUtils);
int featureCnt = 3;
InternalProtocolBuilder protocolBuilder = new InternalProtocolBuilder();
addFeatures(protocolBuilder, featureCnt, 1);
List<StreamStructure> streamStructures = protocolBuilder.buildAsStreamStructure();
StreamToken token =
StreamToken.newBuilder().setContentId(contentIdGenerators.createTokenContentId(2)).build();
// The token needs to be in the session, if not we ignore the update
MutationContext context = new MutationContext.Builder().setContinuationToken(token).build();
headSession.updateSession(false, streamStructures, context);
assertThat(headSession.getContentInSession()).isEmpty();
}
@Test
public void testUpdateSession_remove() {
HeadSessionImpl headSession = new HeadSessionImpl(fakeStore, timingUtils);
int featureCnt = 3;
InternalProtocolBuilder protocolBuilder = new InternalProtocolBuilder().addClearOperation();
addFeatures(protocolBuilder, featureCnt, 1);
protocolBuilder.removeFeature(
contentIdGenerators.createFeatureContentId(1), contentIdGenerators.createRootContentId(0));
List<StreamStructure> streamStructures = protocolBuilder.buildAsStreamStructure();
// 1 clear, 3 features, 1 remove
assertThat(streamStructures).hasSize(5);
headSession.updateSession(false, streamStructures, null);
// expect: 2 features (3 added, then 1 removed)
assertThat(headSession.getContentInSession()).hasSize(featureCnt - 1);
assertThat(headSession.getContentInSession())
.contains(contentIdGenerators.createFeatureContentId(2));
assertThat(headSession.getContentInSession())
.contains(contentIdGenerators.createFeatureContentId(3));
assertThat(getContentInSession()).hasSize(featureCnt - 1);
}
@Test
public void testUpdateSession_updates() {
HeadSessionImpl headSession = new HeadSessionImpl(fakeStore, timingUtils);
int featureCnt = 3;
InternalProtocolBuilder protocolBuilder = new InternalProtocolBuilder().addClearOperation();
addFeatures(protocolBuilder, featureCnt, 1);
List<StreamStructure> streamStructures = protocolBuilder.buildAsStreamStructure();
assertThat(streamStructures).hasSize(4);
// 1 clear, 3 features
headSession.updateSession(false, streamStructures, null);
assertThat(headSession.getContentInSession()).hasSize(featureCnt);
assertThat(getContentInSession()).hasSize(featureCnt);
// Now we will update feature 2
protocolBuilder = new InternalProtocolBuilder();
addFeatures(protocolBuilder, 1, 2);
streamStructures = protocolBuilder.buildAsStreamStructure();
assertThat(streamStructures).hasSize(1);
// 0 features
headSession.updateSession(false, streamStructures, null);
assertThat(headSession.getContentInSession()).hasSize(featureCnt);
assertThat(getContentInSession()).hasSize(featureCnt);
}
@Test
public void testUpdateSession_paging() {
HeadSessionImpl headSession = new HeadSessionImpl(fakeStore, timingUtils);
int featureCnt = 3;
InternalProtocolBuilder protocolBuilder = new InternalProtocolBuilder().addClearOperation();
addFeatures(protocolBuilder, featureCnt, 1);
List<StreamStructure> streamStructures = protocolBuilder.buildAsStreamStructure();
assertThat(streamStructures).hasSize(4);
// 1 clear, 3 features
headSession.updateSession(false, streamStructures, null);
assertThat(headSession.getContentInSession()).hasSize(featureCnt);
assertThat(getContentInSession()).hasSize(featureCnt);
// Now we add two new features
int additionalFeatureCnt = 2;
protocolBuilder = new InternalProtocolBuilder();
addFeatures(protocolBuilder, additionalFeatureCnt, featureCnt + 1);
streamStructures = protocolBuilder.buildAsStreamStructure();
assertThat(streamStructures).hasSize(additionalFeatureCnt);
// 0 features
headSession.updateSession(false, streamStructures, null);
assertThat(headSession.getContentInSession()).hasSize(featureCnt + additionalFeatureCnt);
assertThat(getContentInSession()).hasSize(featureCnt + additionalFeatureCnt);
}
@Test
public void testUpdateSession_clearHead() {
HeadSessionImpl headSession = new HeadSessionImpl(fakeStore, timingUtils);
int featureCnt = 3;
InternalProtocolBuilder protocolBuilder = new InternalProtocolBuilder().addClearOperation();
addFeatures(protocolBuilder, featureCnt, 1);
List<StreamStructure> streamStructures = protocolBuilder.buildAsStreamStructure();
assertThat(streamStructures).hasSize(4);
// 1 clear, 3 features
headSession.updateSession(false, streamStructures, null);
assertThat(headSession.getContentInSession()).hasSize(featureCnt);
assertThat(getContentInSession()).hasSize(featureCnt);
// Clear head and add 2 features, make sure we have new content ids
int newFeatureCnt = 2;
protocolBuilder = new InternalProtocolBuilder().addClearOperation();
addFeatures(protocolBuilder, newFeatureCnt, featureCnt + 1);
streamStructures = protocolBuilder.buildAsStreamStructure();
// 2 features, 1 clear
assertThat(streamStructures).hasSize(3);
// 0 features
fakeStore.clearHead();
headSession.updateSession(false, streamStructures, null);
assertThat(headSession.getContentInSession()).hasSize(newFeatureCnt);
assertThat(getContentInSession()).hasSize(newFeatureCnt);
}
private void addFeatures(InternalProtocolBuilder protocolBuilder, int featureCnt, int startId) {
for (int i = 0; i < featureCnt; i++) {
protocolBuilder.addFeature(
contentIdGenerators.createFeatureContentId(startId++),
contentIdGenerators.createRootContentId(0));
}
}
/** Re-read the session from disk and return the set of content. */
private Set<String> getContentInSession() {
HeadSessionImpl headSession = new HeadSessionImpl(fakeStore, timingUtils);
headSession.initializeSession(fakeStore.getStreamStructures(Store.HEAD_SESSION_ID).getValue());
return headSession.getContentInSession();
}
}