/* * Copyright 2017 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.config; import com.thoughtworks.go.config.commands.EntityConfigUpdateCommand; import com.thoughtworks.go.config.update.FullConfigUpdateCommand; import com.thoughtworks.go.listener.ConfigChangedListener; import com.thoughtworks.go.listener.EntityConfigChangedListener; import com.thoughtworks.go.server.domain.Username; import com.thoughtworks.go.serverhealth.ServerHealthService; import com.thoughtworks.go.serverhealth.ServerHealthState; import com.thoughtworks.go.util.SystemEnvironment; import org.hamcrest.core.Is; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; import static org.mockito.Mockito.*; import static org.mockito.MockitoAnnotations.initMocks; public class CachedGoConfigTest { @Mock private CachedGoConfig cachedGoConfig; @Mock private GoFileConfigDataSource dataSource; private GoConfigHolder configHolder; @Mock private ServerHealthService serverHealthService; @Mock private GoConfigMigrator goConfigMigrator; @Mock private SystemEnvironment systemEnvironment; @Before public void setUp() throws Exception { initMocks(this); configHolder = new GoConfigHolder(new BasicCruiseConfig(), new BasicCruiseConfig()); cachedGoConfig = new CachedGoConfig(serverHealthService, dataSource, mock(CachedGoPartials.class), goConfigMigrator, systemEnvironment); stub(systemEnvironment.optimizeFullConfigSave()).toReturn(true); when(dataSource.load()).thenReturn(configHolder); } @Test public void shouldDelegateWriteEntityConfigCallToDataSource() { EntityConfigUpdateCommand saveCommand = mock(EntityConfigUpdateCommand.class); GoConfigHolder savedConfig = new GoConfigHolder(new BasicCruiseConfig(), new BasicCruiseConfig()); GoConfigHolder holderBeforeUpdate = cachedGoConfig.loadConfigHolder(); Username user = new Username(new CaseInsensitiveString("user")); EntityConfigSaveResult entityConfigSaveResult = mock(EntityConfigSaveResult.class); when(entityConfigSaveResult.getConfigHolder()).thenReturn(savedConfig); when(entityConfigSaveResult.getEntityConfig()).thenReturn(new PipelineConfig()); when(dataSource.writeEntityWithLock(saveCommand, holderBeforeUpdate, user)).thenReturn(entityConfigSaveResult); cachedGoConfig.writeEntityWithLock(saveCommand, user); assertThat(cachedGoConfig.loadConfigHolder(), is(savedConfig)); assertThat(cachedGoConfig.currentConfig(), is(savedConfig.config)); assertThat(cachedGoConfig.loadForEditing(), is(savedConfig.configForEdit)); verify(dataSource).writeEntityWithLock(saveCommand, holderBeforeUpdate, user); } @Test public void shouldLoadConfigHolderIfNotAvailable() throws Exception { cachedGoConfig.forceReload(); Assert.assertThat(cachedGoConfig.loadConfigHolder(), is(configHolder)); } @Test public void shouldNotifyConfigListenersWhenConfigChanges() throws Exception { when(dataSource.writeWithLock(any(UpdateConfigCommand.class), any(GoConfigHolder.class))).thenReturn(new GoFileConfigDataSource.GoConfigSaveResult(configHolder, ConfigSaveState.UPDATED)); final ConfigChangedListener listener = mock(ConfigChangedListener.class); cachedGoConfig.registerListener(listener); cachedGoConfig.forceReload(); cachedGoConfig.writeWithLock(new UpdateConfigCommand() { @Override public CruiseConfig update(CruiseConfig cruiseConfig) throws Exception { return cruiseConfig; } }); verify(listener, times(2)).onConfigChange(any(BasicCruiseConfig.class)); } @Test public void shouldNotNotifyWhenConfigIsNullDuringRegistration() throws Exception { final ConfigChangedListener listener = mock(ConfigChangedListener.class); cachedGoConfig.registerListener(listener); verifyNoMoreInteractions(listener); } @Test public void shouldNotifyConcernedListenersWhenEntityChanges() { final boolean[] pipelineConfigChangeListenerCalled = {false}; final boolean[] agentConfigChangeListenerCalled = {false}; final boolean[] cruiseConfigChangeListenerCalled = {false}; EntityConfigChangedListener<PipelineConfig> pipelineConfigChangeListener = new EntityConfigChangedListener<PipelineConfig>() { @Override public void onEntityConfigChange(PipelineConfig entity) { pipelineConfigChangeListenerCalled[0] = true; } }; EntityConfigChangedListener<AgentConfig> agentConfigChangeListener = new EntityConfigChangedListener<AgentConfig>() { @Override public void onEntityConfigChange(AgentConfig entity) { agentConfigChangeListenerCalled[0] = true; } }; EntityConfigChangedListener<CruiseConfig> cruiseConfigChangeListener = new EntityConfigChangedListener<CruiseConfig>() { @Override public void onEntityConfigChange(CruiseConfig entity) { cruiseConfigChangeListenerCalled[0] = true; } }; cachedGoConfig.registerListener(pipelineConfigChangeListener); cachedGoConfig.registerListener(agentConfigChangeListener); cachedGoConfig.registerListener(cruiseConfigChangeListener); EntityConfigUpdateCommand configCommand = mock(EntityConfigUpdateCommand.class); when(configCommand.isValid(any(CruiseConfig.class))).thenReturn(true); when(configCommand.getPreprocessedEntityConfig()).thenReturn(mock(PipelineConfig.class)); EntityConfigSaveResult entityConfigSaveResult = mock(EntityConfigSaveResult.class); when(entityConfigSaveResult.getConfigHolder()).thenReturn(configHolder); when(entityConfigSaveResult.getEntityConfig()).thenReturn(new PipelineConfig()); Username user = new Username(new CaseInsensitiveString("user")); when(dataSource.writeEntityWithLock(configCommand, configHolder, user)).thenReturn(entityConfigSaveResult); cachedGoConfig.loadConfigIfNull(); cachedGoConfig.writeEntityWithLock(configCommand, user); assertThat(pipelineConfigChangeListenerCalled[0], is(true)); assertThat(agentConfigChangeListenerCalled[0], is(false)); assertThat(cruiseConfigChangeListenerCalled[0], is(false)); } @Test public void shouldWriteFullConfigWithLock() { FullConfigUpdateCommand fullConfigUpdateCommand = mock(FullConfigUpdateCommand.class); when(dataSource.writeFullConfigWithLock(any(FullConfigUpdateCommand.class), any(GoConfigHolder.class))).thenReturn(new GoFileConfigDataSource.GoConfigSaveResult(null, null)); cachedGoConfig.forceReload(); cachedGoConfig.writeFullConfigWithLock(fullConfigUpdateCommand); verify(dataSource).writeFullConfigWithLock(fullConfigUpdateCommand, cachedGoConfig.loadConfigHolder()); } @Test public void shouldUpdateCachesPostWriteFullConfigWithLock() { BasicCruiseConfig config = mock(BasicCruiseConfig.class); BasicCruiseConfig configForEdit = mock(BasicCruiseConfig.class); BasicCruiseConfig mergedConfigForEdit = mock(BasicCruiseConfig.class); GoConfigHolder goConfigHolder = new GoConfigHolder(config, configForEdit); goConfigHolder.mergedConfigForEdit = mergedConfigForEdit; ConfigSaveState configSaveState = ConfigSaveState.UPDATED; when(dataSource.writeFullConfigWithLock(any(FullConfigUpdateCommand.class), any(GoConfigHolder.class))) .thenReturn(new GoFileConfigDataSource.GoConfigSaveResult(goConfigHolder, configSaveState)); cachedGoConfig.forceReload(); ConfigSaveState saveState = cachedGoConfig.writeFullConfigWithLock(mock(FullConfigUpdateCommand.class)); assertThat(saveState, is(configSaveState)); assertThat(cachedGoConfig.currentConfig(), Is.<CruiseConfig>is(config)); assertThat(cachedGoConfig.loadForEditing(), Is.<CruiseConfig>is(configForEdit)); assertThat(cachedGoConfig.loadConfigHolder(), is(goConfigHolder)); assertThat(cachedGoConfig.loadMergedForEditing(), Is.<CruiseConfig>is(mergedConfigForEdit)); verify(serverHealthService, times(2)).update(any(ServerHealthState.class)); } @Test public void shouldUpgradeConfigFile() throws Exception { cachedGoConfig.upgradeConfig(); verify(goConfigMigrator).migrate(); } @Test public void shouldUpdateCachesPostConfigUpgade() throws Exception { BasicCruiseConfig config = mock(BasicCruiseConfig.class); BasicCruiseConfig configForEdit = mock(BasicCruiseConfig.class); BasicCruiseConfig mergedConfigForEdit = mock(BasicCruiseConfig.class); GoConfigHolder goConfigHolder = new GoConfigHolder(config, configForEdit); goConfigHolder.mergedConfigForEdit = mergedConfigForEdit; when(goConfigMigrator.migrate()).thenReturn(goConfigHolder); cachedGoConfig.upgradeConfig(); assertThat(cachedGoConfig.currentConfig(), Is.<CruiseConfig>is(config)); assertThat(cachedGoConfig.loadForEditing(), Is.<CruiseConfig>is(configForEdit)); assertThat(cachedGoConfig.loadConfigHolder(), is(goConfigHolder)); assertThat(cachedGoConfig.loadMergedForEditing(), Is.<CruiseConfig>is(mergedConfigForEdit)); verify(serverHealthService).update(any(ServerHealthState.class)); } @Test public void shouldFallbackToOldConfigUpgradeIfNewFlowIsDisabled() throws Exception { when(systemEnvironment.optimizeFullConfigSave()).thenReturn(false); cachedGoConfig.upgradeConfig(); verify(dataSource).upgradeIfNecessary(); } }