ContextRefresherConfigTreeTests.java

/*
 * Copyright 2012-present the original author or 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
 *
 *      https://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 org.springframework.cloud.context.refresh;

import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
import java.util.Set;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.Mockito;

import org.springframework.boot.env.ConfigTreePropertySource;
import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration;
import org.springframework.cloud.context.scope.refresh.RefreshScope;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.StandardEnvironment;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;

/**
 * @author arimu1
 */
class ContextRefresherConfigTreeTests {

	private static final String CONFIG_TREE_SOURCE = "configtree";

	@TempDir
	Path configDir;

	@Test
	void configTreePropertyValuesWithoutEqualsAreComparedByContent() throws IOException {
		Files.writeString(configDir.resolve("app.message"), "hello");

		ConfigTreePropertySource first = new ConfigTreePropertySource(CONFIG_TREE_SOURCE, configDir);
		ConfigTreePropertySource second = new ConfigTreePropertySource(CONFIG_TREE_SOURCE, configDir);

		Object firstValue = first.getProperty("app.message");
		Object secondValue = second.getProperty("app.message");
		assertThat(firstValue).isNotNull();
		assertThat(Objects.equals(firstValue, secondValue)).isFalse();
		assertThat(firstValue.toString()).isEqualTo(secondValue.toString());
	}

	@Test
	void refreshEnvironmentDoesNotReportUnchangedConfigTreeProperties() throws IOException {
		Files.writeString(configDir.resolve("app.message"), "hello");

		ConfigurableEnvironment environment = new StandardEnvironment();
		environment.getPropertySources().addFirst(new ConfigTreePropertySource(CONFIG_TREE_SOURCE, configDir));

		ConfigurableApplicationContext context = Mockito.mock(ConfigurableApplicationContext.class);
		when(context.getEnvironment()).thenReturn(environment);

		RefreshScope scope = Mockito.mock(RefreshScope.class);
		ContextRefresher refresher = new ConfigDataContextRefresher(context, scope,
				new RefreshAutoConfiguration.RefreshProperties()) {
			@Override
			protected void updateEnvironment() {
				environment.getPropertySources()
					.replace(CONFIG_TREE_SOURCE, new ConfigTreePropertySource(CONFIG_TREE_SOURCE, configDir));
			}
		};

		Set<String> changedKeys = refresher.refreshEnvironment();

		assertThat(changedKeys).isEmpty();
	}

	@Test
	void refreshEnvironmentReportsChangedConfigTreePropertyContent() throws IOException {
		Path beforeDir = Files.createTempDirectory(configDir, "before");
		Path afterDir = Files.createTempDirectory(configDir, "after");
		Files.writeString(beforeDir.resolve("app.message"), "hello");
		Files.writeString(afterDir.resolve("app.message"), "goodbye");

		ConfigurableEnvironment environment = new StandardEnvironment();
		environment.getPropertySources().addFirst(new ConfigTreePropertySource(CONFIG_TREE_SOURCE, beforeDir));

		ConfigurableApplicationContext context = Mockito.mock(ConfigurableApplicationContext.class);
		when(context.getEnvironment()).thenReturn(environment);

		RefreshScope scope = Mockito.mock(RefreshScope.class);
		ContextRefresher refresher = new ConfigDataContextRefresher(context, scope,
				new RefreshAutoConfiguration.RefreshProperties()) {
			@Override
			protected void updateEnvironment() {
				environment.getPropertySources()
					.replace(CONFIG_TREE_SOURCE, new ConfigTreePropertySource(CONFIG_TREE_SOURCE, afterDir));
			}
		};

		Set<String> changedKeys = refresher.refreshEnvironment();

		assertThat(changedKeys).containsExactly("app.message");
	}

	@Test
	void refreshEnvironmentDoesNotReportUnchangedBinaryConfigTreeProperties() throws IOException {
		byte[] certificate = new byte[] { 0x30, (byte) 0x82, 0x01, 0x00, 0x00, 0x01, (byte) 0xFF };
		Files.write(configDir.resolve("app.cert"), certificate);

		ConfigurableEnvironment environment = new StandardEnvironment();
		environment.getPropertySources().addFirst(new ConfigTreePropertySource(CONFIG_TREE_SOURCE, configDir));

		ConfigurableApplicationContext context = Mockito.mock(ConfigurableApplicationContext.class);
		when(context.getEnvironment()).thenReturn(environment);

		RefreshScope scope = Mockito.mock(RefreshScope.class);
		ContextRefresher refresher = new ConfigDataContextRefresher(context, scope,
				new RefreshAutoConfiguration.RefreshProperties()) {
			@Override
			protected void updateEnvironment() {
				environment.getPropertySources()
					.replace(CONFIG_TREE_SOURCE, new ConfigTreePropertySource(CONFIG_TREE_SOURCE, configDir));
			}
		};

		Set<String> changedKeys = refresher.refreshEnvironment();

		assertThat(changedKeys).isEmpty();
	}

	@Test
	void equalReturnsFalseWhenConfigTreeFileIsMissingDuringComparison() throws Exception {
		Files.writeString(configDir.resolve("app.message"), "hello");

		ConfigTreePropertySource first = new ConfigTreePropertySource(CONFIG_TREE_SOURCE, configDir,
				ConfigTreePropertySource.Option.ALWAYS_READ);
		ConfigTreePropertySource second = new ConfigTreePropertySource(CONFIG_TREE_SOURCE, configDir,
				ConfigTreePropertySource.Option.ALWAYS_READ);
		Object beforeValue = first.getProperty("app.message");
		Object afterValue = second.getProperty("app.message");

		Files.delete(configDir.resolve("app.message"));

		ConfigurableApplicationContext context = Mockito.mock(ConfigurableApplicationContext.class);
		when(context.getEnvironment()).thenReturn(new StandardEnvironment());
		ContextRefresher refresher = new ConfigDataContextRefresher(context, Mockito.mock(RefreshScope.class),
				new RefreshAutoConfiguration.RefreshProperties());

		Method equal = ContextRefresher.class.getDeclaredMethod("equal", Object.class, Object.class);
		equal.setAccessible(true);

		assertThat((boolean) equal.invoke(refresher, beforeValue, afterValue)).isFalse();
	}

}