package org.togglz.googleclouddatastore.repository; import com.google.cloud.datastore.BooleanValue; import com.google.cloud.datastore.Datastore; import com.google.cloud.datastore.Entity; import com.google.cloud.datastore.Key; import com.google.cloud.datastore.StringValue; import com.google.cloud.datastore.Transaction; import com.google.cloud.datastore.Value; import com.google.cloud.datastore.testing.LocalDatastoreHelper; import com.google.common.collect.ImmutableMap; import org.joda.time.Duration; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.togglz.core.Feature; import org.togglz.core.repository.FeatureState; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.TimeoutException; import static java.util.Collections.singletonList; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.togglz.googleclouddatastore.repository.GoogleCloudDatastoreStateRepository.KIND_DEFAULT; public class GoogleCloudDatastoreStateRepositoryIT { private static final int MAX_ENTITY_GROUPS = 25; private static final LocalDatastoreHelper HELPER = LocalDatastoreHelper.create(1.0); private static final Datastore DATASTORE = HELPER.getOptions().getService(); private static final String STRATEGY_ID = "myStrategy"; private GoogleCloudDatastoreStateRepository repository; @BeforeClass public static void beforeClass() throws IOException, InterruptedException { HELPER.start(); } @Before public void setUp() throws Exception { repository = new GoogleCloudDatastoreStateRepository(DATASTORE); } @AfterClass public static void afterClass() throws IOException, InterruptedException, TimeoutException { HELPER.stop(Duration.standardMinutes(1)); } @After public void tearDown() throws Exception { HELPER.reset(); } @Test public void shouldUseGiveKindWhenPersisting() { // GIVEN a repo with a custom kind final String kind = "CustomKind"; repository = new GoogleCloudDatastoreStateRepository(DATASTORE, kind); // WHEN a feature is persisted final FeatureState state = new FeatureState(TestFeature.F1); repository.setFeatureState(state); // THEN new entities should be persisted within it final Key key = DATASTORE.newKeyFactory().setKind(kind).newKey(TestFeature.F1.name()); final Entity entity = DATASTORE.get(key); assertNotNull(entity); } @Test public void testShouldSaveStateWithoutStrategyOrParameters() { //WHEN a feature without strategy is persisted final FeatureState state = new FeatureState(TestFeature.F1).disable(); repository.setFeatureState(state); //THEN there should be a corresponding entry in the database final Key key = createKey(TestFeature.F1.name()); final Entity featureEntity = DATASTORE.get(key); assertFalse(featureEntity.getBoolean(GoogleCloudDatastoreStateRepository.ENABLED)); assertFalse(featureEntity.contains(GoogleCloudDatastoreStateRepository.STRATEGY_ID)); assertFalse(featureEntity.contains(GoogleCloudDatastoreStateRepository.STRATEGY_PARAMS_NAMES)); assertFalse(featureEntity.contains(GoogleCloudDatastoreStateRepository.STRATEGY_PARAMS_VALUES)); } @Test public void testShouldSaveStateStrategyAndParameters() { // WHEN a feature without strategy is persisted final FeatureState state = new FeatureState(TestFeature.F1) .enable() .setStrategyId("someId") .setParameter("param", "foo"); repository.setFeatureState(state); // THEN there should be a corresponding entry in the database final Key key = createKey(TestFeature.F1.name()); final Entity featureEntity = DATASTORE.get(key); assertTrue(featureEntity.getBoolean(GoogleCloudDatastoreStateRepository.ENABLED)); assertEquals("someId", featureEntity.getString(GoogleCloudDatastoreStateRepository.STRATEGY_ID)); final StringValue param = NonIndexed.valueOf("param"); assertThat(featureEntity.<StringValue>getList(GoogleCloudDatastoreStateRepository.STRATEGY_PARAMS_NAMES), is(singletonList(param))); final StringValue foo = NonIndexed.valueOf("foo"); assertThat(featureEntity.<StringValue>getList(GoogleCloudDatastoreStateRepository.STRATEGY_PARAMS_VALUES), is(singletonList(foo))); } @Test public void shouldReturnNullWhenStateDoesntExist() { // GIVEN there is no feature state in the DATASTORE WHEN the repository reads the state final FeatureState state = repository.getFeatureState(TestFeature.F1); // THEN the properties should be set like expected assertNull(state); } @Test public void testShouldReadStateWithoutStrategyAndParameters() { // GIVEN a database row containing a simple feature state givenDisabledFeature("F1"); // WHEN the repository reads the state final FeatureState state = repository.getFeatureState(TestFeature.F1); // THEN the properties should be set like expected assertNotNull(state); assertEquals(TestFeature.F1, state.getFeature()); assertEquals(false, state.isEnabled()); assertEquals(null, state.getStrategyId()); assertEquals(0, state.getParameterNames().size()); } @Test public void testShouldReadStateWithStrategyAndParameters() { // GIVEN a database row containing a simple feature state givenEnabledFeatureWithStrategy("F1"); // WHEN the repository reads the state final FeatureState state = repository.getFeatureState(TestFeature.F1); // THEN the properties should be set like expected assertNotNull(state); assertEquals(TestFeature.F1, state.getFeature()); assertEquals(true, state.isEnabled()); assertEquals(STRATEGY_ID, state.getStrategyId()); assertEquals(1, state.getParameterNames().size()); assertEquals("foobar", state.getParameter("param23")); } @Test public void testShouldUpdateExistingDatabaseEntry() { // GIVEN a database row containing a simple feature state givenEnabledFeatureWithStrategy("F1"); // AND the database entries are like expected // THEN there should be a corresponding entry in the database final Key key = createKey(TestFeature.F1.name()); Entity featureEntity = DATASTORE.get(key); assertTrue(featureEntity.getBoolean(GoogleCloudDatastoreStateRepository.ENABLED)); assertEquals(STRATEGY_ID, featureEntity.getString(GoogleCloudDatastoreStateRepository.STRATEGY_ID)); StringValue param = NonIndexed.valueOf("param23"); assertThat(featureEntity.<StringValue>getList(GoogleCloudDatastoreStateRepository.STRATEGY_PARAMS_NAMES), is(singletonList(param))); StringValue foo = NonIndexed.valueOf("foobar"); assertThat(featureEntity.<StringValue>getList(GoogleCloudDatastoreStateRepository.STRATEGY_PARAMS_VALUES), is(singletonList(foo))); // WHEN the repository writes new state final FeatureState state = new FeatureState(TestFeature.F1) .disable() .setStrategyId("someId") .setParameter("param", "foo"); repository.setFeatureState(state); // THEN the properties should be set like expected featureEntity = DATASTORE.get(key); assertEquals(false, featureEntity.getBoolean(GoogleCloudDatastoreStateRepository.ENABLED)); assertEquals("someId", featureEntity.getString(GoogleCloudDatastoreStateRepository.STRATEGY_ID)); param = NonIndexed.valueOf("param"); assertThat(featureEntity.<StringValue>getList(GoogleCloudDatastoreStateRepository.STRATEGY_PARAMS_NAMES), is(singletonList(param))); foo = NonIndexed.valueOf("foo"); assertThat(featureEntity.<StringValue>getList(GoogleCloudDatastoreStateRepository.STRATEGY_PARAMS_VALUES), is(singletonList(foo))); } @Test public void shouldNotAddNewEntityGroupToCurrentCrossGroupTransaction() { givenDisabledFeature("F"); final Transaction txn = DATASTORE.newTransaction(); for (int i = 0; i < MAX_ENTITY_GROUPS - 1; i++) { putWithinTransaction("F" + i, false, txn); } putWithinTransaction("F", false, txn); repository.getFeatureState(TestFeature.F1); txn.commit(); } @Test public void shouldWorkInsideRunningTransaction() { givenDisabledFeature("F1"); final Transaction txn = DATASTORE.newTransaction(); putWithinTransaction("F3", false, txn); repository.getFeatureState(TestFeature.F1); txn.commit(); } private Key createKey(String name) { return DATASTORE.newKeyFactory().setKind(KIND_DEFAULT).newKey(name); } private void givenDisabledFeature(String featureName) { put(featureName, false, null, null, null); } private void givenEnabledFeatureWithStrategy(String featureName) { put(featureName, true, STRATEGY_ID, ImmutableMap.of("param23", "foobar")); } private void putWithinTransaction(final String name, final boolean enabled, final Transaction txn) { put(name, enabled, null, null, txn); } private void put(final String name, final boolean enabled, final String strategyId, final Map<String, String> params) { put(name, enabled, strategyId, params, null); } private void put(final String name, final boolean enabled, final String strategyId, final Map<String, String> params, final Transaction txn) { final Key key = createKey(name); final Entity.Builder builder = Entity.newBuilder(key) .set(GoogleCloudDatastoreStateRepository.ENABLED, BooleanValue.newBuilder(enabled).setExcludeFromIndexes(true).build()); if (strategyId != null) { builder.set(GoogleCloudDatastoreStateRepository.STRATEGY_ID, StringValue.newBuilder(strategyId).setExcludeFromIndexes(true).build()); } if (params != null && !params.isEmpty()) { final List<Value<String>> strategyParamsNames = new ArrayList<>(params.size()); final List<Value<String>> strategyParamsValues = new ArrayList<>(params.size()); for (final String paramName : params.keySet()) { strategyParamsNames.add(StringValue.newBuilder(paramName).setExcludeFromIndexes(true).build()); strategyParamsValues.add(StringValue.newBuilder(params.get(paramName)).setExcludeFromIndexes(true).build()); } builder.set(GoogleCloudDatastoreStateRepository.STRATEGY_PARAMS_NAMES, strategyParamsNames); builder.set(GoogleCloudDatastoreStateRepository.STRATEGY_PARAMS_VALUES, strategyParamsValues); } if (txn == null) { DATASTORE.put(builder.build()); } else { txn.put(builder.build()); } } private enum TestFeature implements Feature { F1 } }