com.thoughtworks.go.server.service.support.toggle.FeatureToggleRepositoryTest.java Source code

Java tutorial

Introduction

Here is the source code for com.thoughtworks.go.server.service.support.toggle.FeatureToggleRepositoryTest.java

Source

/*
 * Copyright 2019 ThoughtWorks, Inc.
 *
 * 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.thoughtworks.go.server.service.support.toggle;

import com.google.gson.Gson;
import com.thoughtworks.go.server.domain.support.toggle.FeatureToggle;
import com.thoughtworks.go.server.domain.support.toggle.FeatureToggles;
import com.thoughtworks.go.util.SystemEnvironment;
import com.thoughtworks.go.util.TestFileUtil;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.mockito.Mock;

import java.io.File;
import java.net.URISyntaxException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;

import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;

public class FeatureToggleRepositoryTest {
    public static final String TEST_AVAILABLE_TOGGLES_PATH = "/available.test.toggles";

    @Rule
    public final TemporaryFolder temporaryFolder = new TemporaryFolder();

    @Mock
    private SystemEnvironment environment;

    @Before
    public void setUp() throws Exception {
        initMocks(this);
    }

    @After
    public void tearDown() throws Exception {
        FileUtils.writeStringToFile(availableTogglesFile(), "", UTF_8);
    }

    @Test
    public void shouldReadFeatureTogglesFromAvailableTogglesFile() throws Exception {
        FeatureToggle featureToggle1 = new FeatureToggle("key1", "desc1", true);
        FeatureToggle featureToggle2 = new FeatureToggle("key2", "desc2", false);

        setupAvailableToggles(featureToggle1, featureToggle2);

        FeatureToggleRepository repository = new FeatureToggleRepository(environment);

        assertThat(repository.availableToggles(), is(new FeatureToggles(featureToggle1, featureToggle2)));
    }

    @Test
    public void shouldNotFailWhenSpecifiedAvailableTogglesFileIsNotFound() throws Exception {
        setupAvailableToggleFileAs("a-non-existent-file");

        FeatureToggleRepository repository = new FeatureToggleRepository(environment);

        assertThat(repository.availableToggles(), is(new FeatureToggles()));
    }

    @Test
    public void shouldNotFailWhenContentOfAvailableTogglesFileIsInvalid() throws Exception {
        setupAvailableToggleFileAs(TEST_AVAILABLE_TOGGLES_PATH);
        FileUtils.writeStringToFile(availableTogglesFile(), "SOME-INVALID-CONTENT", UTF_8);

        FeatureToggleRepository repository = new FeatureToggleRepository(environment);

        assertThat(repository.availableToggles(), is(new FeatureToggles()));
    }

    @Test
    public void shouldReadFeatureTogglesFromUsersTogglesFile() throws Exception {
        FeatureToggle featureToggle1 = new FeatureToggle("key1", "desc1", true);
        FeatureToggle featureToggle2 = new FeatureToggle("key2", "desc2", false);

        setupUserToggles(featureToggle1, featureToggle2);

        FeatureToggleRepository repository = new FeatureToggleRepository(environment);

        assertThat(repository.userToggles(), is(new FeatureToggles(featureToggle1, featureToggle2)));
    }

    @Test
    public void shouldNotFailWhenSpecifiedUserTogglesFileIsNotFound() throws Exception {
        setupUserToggleFileAs(new File("a-non-existent-file"));

        FeatureToggleRepository repository = new FeatureToggleRepository(environment);

        assertThat(repository.userToggles(), is(new FeatureToggles()));
    }

    @Test
    public void shouldNotFailWhenContentOfUserTogglesFileIsInvalid() throws Exception {
        File toggleFile = TestFileUtil.createTempFile("available.toggle.test");
        FileUtils.writeStringToFile(toggleFile, "SOME-INVALID-CONTENT", UTF_8);
        setupUserToggleFileAs(toggleFile);

        FeatureToggleRepository repository = new FeatureToggleRepository(environment);

        assertThat(repository.userToggles(), is(new FeatureToggles()));
    }

    @Test
    public void shouldAllowChangingValueOfAToggleWhenTheUserTogglesFileDoesNotExist() throws Exception {
        File togglesDir = temporaryFolder.newFolder("toggles.dir");
        File nonExistentUserToggleFile = new File(togglesDir, "a-non-existent-file");
        setupUserToggleFileAs(nonExistentUserToggleFile);
        setupAvailableToggles(new FeatureToggle("key1", "desc1", true));

        FeatureToggleRepository repository = new FeatureToggleRepository(environment);
        repository.changeValueOfToggle("key1", false);

        assertThat(repository.availableToggles(), is(new FeatureToggles(new FeatureToggle("key1", "desc1", true))));
        assertThat(repository.userToggles(), is(new FeatureToggles(new FeatureToggle("key1", null, false))));
    }

    @Test
    public void shouldAllowChangingValueOfAToggleWhenTheUserTogglesFileDoesExist() throws Exception {
        setupAvailableToggles(new FeatureToggle("key1", "desc1", true), new FeatureToggle("key2", "desc2", true));
        setupUserToggles(new FeatureToggle("key1", "desc1", true));

        FeatureToggleRepository repository = new FeatureToggleRepository(environment);
        repository.changeValueOfToggle("key1", false);

        assertThat(repository.availableToggles(), is(new FeatureToggles(new FeatureToggle("key1", "desc1", true),
                new FeatureToggle("key2", "desc2", true))));
        assertThat(repository.userToggles(), is(new FeatureToggles(new FeatureToggle("key1", "desc1", false))));
    }

    @Test
    public void shouldFailWhenUnableToWriteToUserTogglesFile_DuringChangingOfAToggleValue() throws Exception {
        setupAvailableToggles(new FeatureToggle("key1", "desc1", true));

        File userTogglesFile = setupUserToggles(new FeatureToggle("key1", "desc1", true));
        userTogglesFile.setReadOnly();

        FeatureToggleRepository repository = new FeatureToggleRepository(environment);

        try {
            repository.changeValueOfToggle("key1", false);
            fail("Should have failed to write");
        } catch (RuntimeException e) {
            assertThat(e.getMessage(), containsString(userTogglesFile.getPath()));
        }
    }

    @Test
    public void whileChangingAToggleValue_shouldNotPersist_ValueHasBeenChangedFlag() throws Exception {
        String fieldForHasBeenChangedFlag = "hasBeenChangedFromDefault";
        assertNotNull(
                "This can never be null, but can throw an exception. If you've renamed the field mentioned above"
                        + "(in FeatureToggle class), please change it in this test too. Otherwise, this test can pass, wrongly.",
                FeatureToggle.class.getDeclaredField(fieldForHasBeenChangedFlag));

        setupAvailableToggles(new FeatureToggle("key1", "desc1", true));
        File userTogglesFile = setupUserToggles(
                new FeatureToggle("key1", "desc1", false).withValueHasBeenChangedFlag(true));

        FeatureToggleRepository repository = new FeatureToggleRepository(environment);
        repository.changeValueOfToggle("key1", false);

        assertThat(repository.userToggles(), is(
                new FeatureToggles(new FeatureToggle("key1", "desc1", false).withValueHasBeenChangedFlag(false))));
        assertThat(FileUtils.readFileToString(userTogglesFile, UTF_8), containsString("key1"));
        assertThat(FileUtils.readFileToString(userTogglesFile, UTF_8), containsString("desc1"));
        assertThat(FileUtils.readFileToString(userTogglesFile, UTF_8),
                not(containsString(fieldForHasBeenChangedFlag)));

        /* The first time the file is written, it is written by hand in this test. Force it to write again,
         * so that the actual JSON write logic is used.
         */
        repository.changeValueOfToggle("key1", true);

        assertThat(repository.userToggles(), is(
                new FeatureToggles(new FeatureToggle("key1", "desc1", true).withValueHasBeenChangedFlag(false))));
        assertThat(FileUtils.readFileToString(userTogglesFile, UTF_8), containsString("key1"));
        assertThat(FileUtils.readFileToString(userTogglesFile, UTF_8), containsString("desc1"));
        assertThat(FileUtils.readFileToString(userTogglesFile, UTF_8),
                not(containsString(fieldForHasBeenChangedFlag)));
    }

    @Test
    public void ensureThatTheRealTogglesFileIsValid() throws Exception {
        String realAvailableTogglesFilePath = new SystemEnvironment()
                .get(SystemEnvironment.AVAILABLE_FEATURE_TOGGLES_FILE_PATH);
        File realAvailableTogglesFile = new File(getClass().getResource(realAvailableTogglesFilePath).toURI());
        String currentContentOfRealAvailableTogglesFile = FileUtils.readFileToString(realAvailableTogglesFile,
                UTF_8);

        try {
            new Gson().fromJson(currentContentOfRealAvailableTogglesFile,
                    FeatureToggleRepository.FeatureToggleFileContentRepresentation.class);
        } catch (Exception e) {
            fail("Check contents of " + realAvailableTogglesFilePath
                    + ". Contents should be valid and be equivalent"
                    + " to FeatureToggleRepository.FeatureToggleFileContentRepresentation.class. Contents were:\n"
                    + currentContentOfRealAvailableTogglesFile + "\n. Exception was: " + e.getMessage());
        }
    }

    private void setupAvailableToggleFileAs(String fileResourcePath) {
        when(environment.get(SystemEnvironment.AVAILABLE_FEATURE_TOGGLES_FILE_PATH)).thenReturn(fileResourcePath);
    }

    private void setupUserToggleFileAs(File file) {
        when(environment.configDir()).thenReturn(file.getParentFile());
        when(environment.get(SystemEnvironment.USER_FEATURE_TOGGLES_FILE_PATH_RELATIVE_TO_CONFIG_DIR))
                .thenReturn(file.getName());
    }

    private void setupAvailableToggles(FeatureToggle... toggles) throws Exception {
        setupAvailableToggleFileAs(TEST_AVAILABLE_TOGGLES_PATH);
        FileUtils.writeStringToFile(availableTogglesFile(), convertTogglesToJson(toggles), UTF_8);
    }

    private File setupUserToggles(FeatureToggle... toggles) throws Exception {
        File toggleFile = temporaryFolder.newFile("user.toggle.test");
        setupUserToggleFileAs(toggleFile);
        FileUtils.writeStringToFile(toggleFile, convertTogglesToJson(toggles), UTF_8);
        return toggleFile;
    }

    /* Write by hand to remove unnecessary coupling to actual write. */
    private String convertTogglesToJson(FeatureToggle[] toggles) {
        List<String> jsonContentForEachToggle = new ArrayList<>();
        for (FeatureToggle toggle : toggles) {
            jsonContentForEachToggle
                    .add(MessageFormat.format("'{'\"key\": \"{0}\", \"description\": \"{1}\", \"value\": {2}'}'",
                            toggle.key(), toggle.description(), String.valueOf(toggle.isOn())));
        }

        return "{ \"version\": \"1\", \"toggles\": [" + StringUtils.join(jsonContentForEachToggle, ",").trim()
                + "]}";
    }

    private File availableTogglesFile() throws URISyntaxException {
        return new File(getClass().getResource(TEST_AVAILABLE_TOGGLES_PATH).toURI());
    }
}