| // 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(); |
| } |
| } |