blob: 45d738dae92dc38a333244317f0829cc0ddf29fa [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.feedstore.internal;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.MockitoAnnotations.initMocks;
import com.google.android.libraries.feed.api.common.ActionPropertiesWithId;
import com.google.android.libraries.feed.api.common.PayloadWithId;
import com.google.android.libraries.feed.api.common.SemanticPropertiesWithId;
import com.google.android.libraries.feed.api.common.ThreadUtils;
import com.google.android.libraries.feed.common.Result;
import com.google.android.libraries.feed.common.concurrent.MainThreadRunner;
import com.google.android.libraries.feed.common.concurrent.testing.FakeMainThreadRunner;
import com.google.android.libraries.feed.common.protoextensions.FeedExtensionRegistry;
import com.google.android.libraries.feed.feedstore.testing.AbstractClearableFeedStoreTest;
import com.google.android.libraries.feed.feedstore.testing.DelegatingContentStorage;
import com.google.android.libraries.feed.feedstore.testing.DelegatingJournalStorage;
import com.google.android.libraries.feed.host.storage.CommitResult;
import com.google.android.libraries.feed.host.storage.ContentMutation;
import com.google.android.libraries.feed.host.storage.ContentStorageDirect;
import com.google.android.libraries.feed.host.storage.JournalMutation;
import com.google.android.libraries.feed.host.storage.JournalStorageDirect;
import com.google.android.libraries.feed.hostimpl.storage.InMemoryContentStorage;
import com.google.android.libraries.feed.hostimpl.storage.InMemoryJournalStorage;
import com.google.android.libraries.feed.internalapi.store.LocalActionMutation.ActionType;
import com.google.android.libraries.feed.internalapi.store.Store;
import com.google.protobuf.ByteString;
import com.google.search.now.feed.client.StreamDataProto.StreamLocalAction;
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.StreamUploadableAction;
import com.google.search.now.wire.feed.OpaqueActionDataForTestProto.OpaqueActionDataForTest;
import com.google.search.now.wire.feed.OpaqueActionDataProto.OpaqueActionData;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
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 com.google.android.libraries.feed.feedstore.internal.PersistentFeedStore}
* class.
*/
@RunWith(RobolectricTestRunner.class)
public class PersistentFeedStoreTest extends AbstractClearableFeedStoreTest {
private ThreadUtils threadUtils = mock(ThreadUtils.class);
private final FeedExtensionRegistry extensionRegistry = new FeedExtensionRegistry(ArrayList::new);
private final ContentStorageDirect contentStorage = new InMemoryContentStorage();
private final JournalStorageDirect journalStorage = new InMemoryJournalStorage();
private FakeMainThreadRunner mainThreadRunner;
@Before
public void setUp() throws Exception {
initMocks(this);
mainThreadRunner = FakeMainThreadRunner.runTasksImmediately();
}
@Override
protected Store getStore(MainThreadRunner mainThreadRunner) {
return new PersistentFeedStore(
timingUtils,
extensionRegistry,
contentStorage,
journalStorage,
threadUtils,
fakeClock,
new FeedStoreHelper());
}
@Test
public void clearStorage_contentStorage_failure_getAllContent() {
ContentStorageDirect contentStorageSpy = spy(new DelegatingContentStorage(contentStorage));
PersistentFeedStore store =
new PersistentFeedStore(
timingUtils,
extensionRegistry,
contentStorageSpy,
journalStorage,
threadUtils,
fakeClock,
new FeedStoreHelper());
doAnswer(ans -> Result.failure()).when(contentStorageSpy).getAllKeys();
boolean clearSuccess = store.clearNonActionContent();
assertThat(clearSuccess).isFalse();
}
@Test
public void clearStorage_contentStorage_failure_commit() {
ContentStorageDirect contentStorageSpy = spy(new DelegatingContentStorage(contentStorage));
PersistentFeedStore store =
new PersistentFeedStore(
timingUtils,
extensionRegistry,
contentStorageSpy,
journalStorage,
threadUtils,
fakeClock,
new FeedStoreHelper());
CommitResult commitResult = store.editContent().add(CONTENT_ID, STREAM_PAYLOAD).commit();
assertThat(commitResult).isEqualTo(CommitResult.SUCCESS);
Result<List<PayloadWithId>> payloadsResult =
store.getPayloads(Collections.singletonList(CONTENT_ID));
assertThat(payloadsResult.isSuccessful()).isTrue();
assertThat(payloadsResult.getValue()).hasSize(1);
assertThat(payloadsResult.getValue().get(0).contentId).isEqualTo(CONTENT_ID);
assertThat(payloadsResult.getValue().get(0).payload).isEqualTo(STREAM_PAYLOAD);
doAnswer(ans -> CommitResult.FAILURE)
.when(contentStorageSpy)
.commit(any(ContentMutation.class));
boolean clearSuccess = store.clearNonActionContent();
assertThat(clearSuccess).isFalse();
}
@Test
public void clearStorage_journalStorage_failure_getAllJournals() {
JournalStorageDirect journalStorageSpy = spy(new DelegatingJournalStorage(journalStorage));
PersistentFeedStore store =
new PersistentFeedStore(
timingUtils,
extensionRegistry,
contentStorage,
journalStorageSpy,
threadUtils,
fakeClock,
new FeedStoreHelper());
boolean commitResult =
store
.editSession(SESSION_ID)
.add(StreamStructure.newBuilder().setContentId(CONTENT_ID).build())
.commit();
assertThat(commitResult).isTrue();
Result<List<String>> sessionsResult = store.getAllSessions();
assertThat(sessionsResult.isSuccessful()).isTrue();
assertThat(sessionsResult.getValue()).hasSize(1);
assertThat(sessionsResult.getValue().get(0)).isEqualTo(SESSION_ID);
doAnswer(ans -> CommitResult.FAILURE)
.when(journalStorageSpy)
.commit(any(JournalMutation.class));
boolean clearSuccess = store.clearNonActionContent();
assertThat(clearSuccess).isFalse();
}
@Test
public void clearStorage_journalStorage_failure_deleteJournal() {
JournalStorageDirect journalStorageSpy = spy(new DelegatingJournalStorage(journalStorage));
PersistentFeedStore store =
new PersistentFeedStore(
timingUtils,
extensionRegistry,
contentStorage,
journalStorageSpy,
threadUtils,
fakeClock,
new FeedStoreHelper());
doAnswer(ans -> Result.failure()).when(journalStorageSpy).getAllJournals();
boolean clearSuccess = store.clearNonActionContent();
assertThat(clearSuccess).isFalse();
}
@Test
public void clearStorage_allStorage() {
PersistentFeedStore store = (PersistentFeedStore) getStore(mainThreadRunner);
/*
SETUP
*/
// Payload
CommitResult commitResult =
store
.editContent()
.add(CONTENT_ID, STREAM_PAYLOAD)
.add(CONTENT_ID_2, STREAM_PAYLOAD_2)
.commit();
assertThat(commitResult).isEqualTo(CommitResult.SUCCESS);
Result<List<PayloadWithId>> payloadsResult =
store.getPayloads(Arrays.asList(CONTENT_ID, CONTENT_ID_2));
assertThat(payloadsResult.isSuccessful()).isTrue();
assertThat(payloadsResult.getValue()).hasSize(2);
// Semantic properties
commitResult =
store
.editSemanticProperties()
.add(CONTENT_ID, ByteString.copyFrom(SEMANTIC_PROPERTIES))
.add(CONTENT_ID_2, ByteString.copyFrom(SEMANTIC_PROPERTIES_2))
.commit();
assertThat(commitResult).isEqualTo(CommitResult.SUCCESS);
Result<List<SemanticPropertiesWithId>> semanticPropertiesResult =
store.getSemanticProperties(Arrays.asList(CONTENT_ID, CONTENT_ID_2));
assertThat(semanticPropertiesResult.isSuccessful()).isTrue();
assertThat(semanticPropertiesResult.getValue()).hasSize(2);
// Action properties
OpaqueActionData data1 =
OpaqueActionData.newBuilder()
.setExtension(
OpaqueActionDataForTest.opaqueActionDataForTestExtension,
OpaqueActionDataForTest.newBuilder().setId(CONTENT_ID).build())
.build();
OpaqueActionData data2 =
OpaqueActionData.newBuilder()
.setExtension(
OpaqueActionDataForTest.opaqueActionDataForTestExtension,
OpaqueActionDataForTest.newBuilder().setId(CONTENT_ID_2).build())
.build();
commitResult =
store.editActionProperties().add(CONTENT_ID, data1).add(CONTENT_ID_2, data2).commit();
assertThat(commitResult).isEqualTo(CommitResult.SUCCESS);
Result<List<ActionPropertiesWithId>> actionPropertiesResult =
store.getActionProperties(Arrays.asList(CONTENT_ID, CONTENT_ID_2));
assertThat(actionPropertiesResult.isSuccessful()).isTrue();
assertThat(actionPropertiesResult.getValue()).hasSize(2);
assertThat(actionPropertiesResult.getValue())
.containsExactly(
new ActionPropertiesWithId(CONTENT_ID, data1),
new ActionPropertiesWithId(CONTENT_ID_2, data2));
// Shared State
commitResult =
store
.editContent()
.add(CONTENT_ID, STREAM_PAYLOAD_SHARED_STATE)
.add(CONTENT_ID_2, STREAM_PAYLOAD_SHARED_STATE_2)
.commit();
assertThat(commitResult).isEqualTo(CommitResult.SUCCESS);
Result<List<StreamSharedState>> sharedStatesResult = store.getSharedStates();
assertThat(sharedStatesResult.isSuccessful()).isTrue();
assertThat(sharedStatesResult.getValue()).hasSize(2);
// Journal
boolean boolCommitResult =
store
.editSession(SESSION_ID)
.add(StreamStructure.newBuilder().setContentId(CONTENT_ID).build())
.commit();
assertThat(boolCommitResult).isTrue();
boolCommitResult =
store
.editSession(SESSION_ID_2)
.add(StreamStructure.newBuilder().setContentId(CONTENT_ID_2).build())
.commit();
assertThat(boolCommitResult).isTrue();
Result<List<String>> sessionsResult = store.getAllSessions();
assertThat(sessionsResult.isSuccessful()).isTrue();
assertThat(sessionsResult.getValue()).hasSize(2);
// Actions
commitResult =
store
.editLocalActions()
.add(ActionType.DISMISS, CONTENT_ID)
.add(ActionType.DISMISS, CONTENT_ID_2)
.commit();
assertThat(commitResult).isEqualTo(CommitResult.SUCCESS);
Result<List<StreamLocalAction>> dismissActionsResult = store.getAllDismissLocalActions();
assertThat(dismissActionsResult.isSuccessful()).isTrue();
assertThat(dismissActionsResult.getValue()).hasSize(2);
commitResult =
store
.editUploadableActions()
.upsert(
StreamUploadableAction.newBuilder().setFeatureContentId(CONTENT_ID).build(),
CONTENT_ID)
.upsert(
StreamUploadableAction.newBuilder().setFeatureContentId(CONTENT_ID_2).build(),
CONTENT_ID_2)
.commit();
assertThat(commitResult).isEqualTo(CommitResult.SUCCESS);
Result<Set<StreamUploadableAction>> uploadableActionsResult = store.getAllUploadableActions();
assertThat(uploadableActionsResult.isSuccessful()).isTrue();
assertThat(uploadableActionsResult.getValue()).hasSize(2);
/*
CLEAR
*/
assertThat(store.clearNonActionContent()).isTrue();
/*
VERIFICATION
*/
// Payload
payloadsResult = store.getPayloads(Collections.singletonList(CONTENT_ID));
assertThat(payloadsResult.isSuccessful()).isTrue();
assertThat(payloadsResult.getValue()).hasSize(0);
// Semantic properties (should not be cleared)
semanticPropertiesResult = store.getSemanticProperties(Arrays.asList(CONTENT_ID, CONTENT_ID_2));
assertThat(semanticPropertiesResult.isSuccessful()).isTrue();
assertThat(semanticPropertiesResult.getValue()).hasSize(2);
assertThat(semanticPropertiesResult.getValue())
.containsExactly(
new SemanticPropertiesWithId(CONTENT_ID, SEMANTIC_PROPERTIES),
new SemanticPropertiesWithId(CONTENT_ID_2, SEMANTIC_PROPERTIES_2));
// Action properties
actionPropertiesResult = store.getActionProperties(Arrays.asList(CONTENT_ID, CONTENT_ID_2));
assertThat(actionPropertiesResult.isSuccessful()).isTrue();
assertThat(actionPropertiesResult.getValue()).hasSize(0);
// Shared state
sharedStatesResult = store.getSharedStates();
assertThat(sharedStatesResult.isSuccessful()).isTrue();
assertThat(sharedStatesResult.getValue()).hasSize(0);
// Journal
sessionsResult = store.getAllSessions();
assertThat(sessionsResult.isSuccessful()).isTrue();
assertThat(sessionsResult.getValue()).hasSize(0);
// Actions (should not be cleared)
dismissActionsResult = store.getAllDismissLocalActions();
assertThat(dismissActionsResult.isSuccessful()).isTrue();
assertThat(dismissActionsResult.getValue()).hasSize(2);
assertThat(dismissActionsResult.getValue().get(0).getFeatureContentId()).isEqualTo(CONTENT_ID);
assertThat(dismissActionsResult.getValue().get(0).getAction()).isEqualTo(ActionType.DISMISS);
assertThat(dismissActionsResult.getValue().get(1).getFeatureContentId())
.isEqualTo(CONTENT_ID_2);
assertThat(dismissActionsResult.getValue().get(1).getAction()).isEqualTo(ActionType.DISMISS);
// UploadableActions (should be cleared)
uploadableActionsResult = store.getAllUploadableActions();
assertThat(uploadableActionsResult.isSuccessful()).isTrue();
assertThat(uploadableActionsResult.getValue()).isEmpty();
}
@Test
public void uploadActions_removedBeforeUpserted_stillUpserts() {
PersistentFeedStore store = (PersistentFeedStore) getStore(mainThreadRunner);
CommitResult commitResult =
store
.editUploadableActions()
.upsert(
StreamUploadableAction.newBuilder().setFeatureContentId(CONTENT_ID).build(),
CONTENT_ID)
.upsert(
StreamUploadableAction.newBuilder().setFeatureContentId(CONTENT_ID_2).build(),
CONTENT_ID_2)
.remove(
StreamUploadableAction.newBuilder().setFeatureContentId(CONTENT_ID_2).build(),
CONTENT_ID_2)
.upsert(
StreamUploadableAction.newBuilder().setFeatureContentId(CONTENT_ID_2).build(),
CONTENT_ID_2)
.commit();
assertThat(commitResult).isEqualTo(CommitResult.SUCCESS);
Result<Set<StreamUploadableAction>> uploadableActionsResult = store.getAllUploadableActions();
assertThat(uploadableActionsResult.isSuccessful()).isTrue();
assertThat(uploadableActionsResult.getValue()).hasSize(2);
assertThat(uploadableActionsResult.getValue())
.containsAllOf(
StreamUploadableAction.newBuilder().setFeatureContentId(CONTENT_ID).build(),
StreamUploadableAction.newBuilder().setFeatureContentId(CONTENT_ID_2).build());
}
@Test
public void uploadActions_removedAfterCommittedUpsert_stillRemoves() {
PersistentFeedStore store = (PersistentFeedStore) getStore(mainThreadRunner);
CommitResult commitResult =
store
.editUploadableActions()
.upsert(
StreamUploadableAction.newBuilder().setFeatureContentId(CONTENT_ID).build(),
CONTENT_ID)
.commit();
assertThat(commitResult).isEqualTo(CommitResult.SUCCESS);
Result<Set<StreamUploadableAction>> uploadableActionsResult = store.getAllUploadableActions();
assertThat(uploadableActionsResult.getValue()).hasSize(1);
assertThat(uploadableActionsResult.getValue())
.contains(StreamUploadableAction.newBuilder().setFeatureContentId(CONTENT_ID).build());
commitResult =
store
.editUploadableActions()
.remove(
StreamUploadableAction.newBuilder().setFeatureContentId(CONTENT_ID).build(),
CONTENT_ID)
.commit();
assertThat(commitResult).isEqualTo(CommitResult.SUCCESS);
uploadableActionsResult = store.getAllUploadableActions();
assertThat(uploadableActionsResult.isSuccessful()).isTrue();
assertThat(uploadableActionsResult.getValue()).isEmpty();
}
@Test
public void uploadActions_removedNonExistantAction_succeeds() {
PersistentFeedStore store = (PersistentFeedStore) getStore(mainThreadRunner);
CommitResult commitResult =
store
.editUploadableActions()
.remove(
StreamUploadableAction.newBuilder().setFeatureContentId(CONTENT_ID).build(),
CONTENT_ID)
.commit();
assertThat(commitResult).isEqualTo(CommitResult.SUCCESS);
Result<Set<StreamUploadableAction>> uploadableActionsResult = store.getAllUploadableActions();
assertThat(uploadableActionsResult.isSuccessful()).isTrue();
assertThat(uploadableActionsResult.getValue()).isEmpty();
}
}