Merged in feature/updatestep_api_for_plugins (pull request #273)

Feature/updatestep api for plugins
This commit is contained in:
Eduard Heimbuch
2019-06-25 12:25:05 +00:00
44 changed files with 828 additions and 164 deletions

View File

@@ -82,7 +82,7 @@ public interface BlobStoreFactory {
final class FloatingStoreParameters implements StoreParameters {
private String name;
private Repository repository;
private String repositoryId;
private final BlobStoreFactory factory;
@@ -96,8 +96,8 @@ final class FloatingStoreParameters implements StoreParameters {
}
@Override
public Repository getRepository() {
return repository;
public String getRepositoryId() {
return repositoryId;
}
public class Builder {
@@ -113,7 +113,18 @@ final class FloatingStoreParameters implements StoreParameters {
* @return Floating API to finish the call.
*/
public FloatingStoreParameters.Builder forRepository(Repository repository) {
FloatingStoreParameters.this.repository = repository;
FloatingStoreParameters.this.repositoryId = repository.getId();
return this;
}
/**
* Use this to create or get a {@link BlobStore} for a specific repository. This step is optional. If you want to
* have a global {@link BlobStore}, omit this.
* @param repositoryId The id of the optional repository for the {@link BlobStore}.
* @return Floating API to finish the call.
*/
public FloatingStoreParameters.Builder forRepository(String repositoryId) {
FloatingStoreParameters.this.repositoryId = repositoryId;
return this;
}

View File

@@ -120,7 +120,18 @@ final class TypedFloatingConfigurationEntryStoreParameters<T> {
* @return Floating API to finish the call.
*/
public OptionalRepositoryBuilder forRepository(Repository repository) {
parameters.setRepository(repository);
parameters.setRepositoryId(repository.getId());
return this;
}
/**
* Use this to create or get a {@link ConfigurationEntryStore} for a specific repository. This step is optional. If
* you want to have a global {@link ConfigurationEntryStore}, omit this.
* @param repositoryId The id of the optional repository for the {@link ConfigurationEntryStore}.
* @return Floating API to finish the call.
*/
public OptionalRepositoryBuilder forRepository(String repositoryId) {
parameters.setRepositoryId(repositoryId);
return this;
}

View File

@@ -120,7 +120,18 @@ final class TypedFloatingConfigurationStoreParameters<T> {
* @return Floating API to finish the call.
*/
public OptionalRepositoryBuilder forRepository(Repository repository) {
parameters.setRepository(repository);
parameters.setRepositoryId(repository.getId());
return this;
}
/**
* Use this to create or get a {@link ConfigurationStore} for a specific repository. This step is optional. If you
* want to have a global {@link ConfigurationStore}, omit this.
* @param repositoryId The id of the optional repository for the {@link ConfigurationStore}.
* @return Floating API to finish the call.
*/
public OptionalRepositoryBuilder forRepository(String repositoryId) {
parameters.setRepositoryId(repositoryId);
return this;
}

View File

@@ -117,7 +117,18 @@ final class TypedFloatingDataStoreParameters<T> {
* @return Floating API to finish the call.
*/
public OptionalRepositoryBuilder forRepository(Repository repository) {
parameters.setRepository(repository);
parameters.setRepositoryId(repository.getId());
return this;
}
/**
* Use this to create or get a {@link DataStore} for a specific repository. This step is optional. If you
* want to have a global {@link DataStore}, omit this.
* @param repositoryId The id of the optional repository for the {@link DataStore}.
* @return Floating API to finish the call.
*/
public OptionalRepositoryBuilder forRepository(String repositoryId) {
parameters.setRepositoryId(repositoryId);
return this;
}

View File

@@ -12,5 +12,5 @@ public interface StoreParameters {
String getName();
Repository getRepository();
String getRepositoryId();
}

View File

@@ -15,5 +15,5 @@ public interface TypedStoreParameters<T> {
String getName();
Repository getRepository();
String getRepositoryId();
}

View File

@@ -1,11 +1,9 @@
package sonia.scm.store;
import sonia.scm.repository.Repository;
class TypedStoreParametersImpl<T> implements TypedStoreParameters<T> {
private Class<T> type;
private String name;
private Repository repository;
private String repositoryId;
@Override
public Class<T> getType() {
@@ -26,11 +24,11 @@ class TypedStoreParametersImpl<T> implements TypedStoreParameters<T> {
}
@Override
public Repository getRepository() {
return repository;
public String getRepositoryId() {
return repositoryId;
}
void setRepository(Repository repository) {
this.repository = repository;
void setRepositoryId(String repositoryId) {
this.repositoryId = repositoryId;
}
}

View File

@@ -0,0 +1,15 @@
package sonia.scm.update;
import java.util.Map;
public class GroupV1PropertyReader implements V1PropertyReader {
@Override
public String getStoreName() {
return "group-properties-v1";
}
@Override
public Instance createInstance(Map<String, V1Properties> all) {
return new MapBasedPropertyReaderInstance(all);
}
}

View File

@@ -0,0 +1,36 @@
package sonia.scm.update;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.stream.Stream;
public class MapBasedPropertyReaderInstance implements V1PropertyReader.Instance {
private final Stream<Map.Entry<String, V1Properties>> all;
public MapBasedPropertyReaderInstance(Map<String, V1Properties> all) {
this(all.entrySet().stream());
}
private MapBasedPropertyReaderInstance(Stream<Map.Entry<String, V1Properties>> all) {
this.all = all;
}
@Override
public void forEachEntry(BiConsumer<String, V1Properties> propertiesForNameConsumer) {
all.forEach(e -> call(e.getKey(), e.getValue(), propertiesForNameConsumer));
}
@Override
public V1PropertyReader.Instance havingAnyOf(String... keys) {
return new MapBasedPropertyReaderInstance(all.filter(e -> e.getValue().hasAny(keys)));
}
@Override
public V1PropertyReader.Instance havingAllOf(String... keys) {
return new MapBasedPropertyReaderInstance(all.filter(e -> e.getValue().hasAll(keys)));
}
private void call(String repositoryId, V1Properties properties, BiConsumer<String, V1Properties> propertiesForNameConsumer) {
propertiesForNameConsumer.accept(repositoryId, properties);
}
}

View File

@@ -0,0 +1,53 @@
package sonia.scm.update;
import java.io.IOException;
import java.nio.file.Path;
public interface PropertyFileAccess {
/**
* Use this to rename a configuration file.
* @param oldName The old file name.
* @return Object to specify the new file name.
*/
Target renameGlobalConfigurationFrom(String oldName);
interface Target {
/**
* Renames a file to the new name given here.
* @param newName The new file name.
* @throws IOException If the file could not be renamed.
*/
void to(String newName) throws IOException;
}
/**
* Creates a tool object for store migration from v1 to v2.
* @param name The name of the store to be handled.
* @return The tool object for the named store.
*/
StoreFileTools forStoreName(String name);
interface StoreFileTools {
/**
* Iterates over all found store files (that is, files ending with ".xml") and calls the
* given consumer for each file, giving the file and the name of the data file.
* @param storeFileConsumer This consumer will be called for each file found.
* @throws IOException May be thrown when an exception occurs.
*/
void forStoreFiles(FileConsumer storeFileConsumer) throws IOException;
/**
* Moves a data file to the new location for a repository with the given id. If there
* is no directory for the given repository id, nothing will be done.
* @param storeFile The name of the store file.
* @param repositoryId The id of the repository as the new target for the store file.
* @throws IOException When the file could not be moved.
*/
void moveAsRepositoryStore(Path storeFile, String repositoryId) throws IOException;
}
interface FileConsumer {
void accept(Path file, String storeName) throws IOException;
}
}

View File

@@ -0,0 +1,16 @@
package sonia.scm.update;
import java.util.Map;
public class RepositoryV1PropertyReader implements V1PropertyReader {
@Override
public String getStoreName() {
return "repository-properties-v1";
}
@Override
public Instance createInstance(Map<String, V1Properties> all) {
return new MapBasedPropertyReaderInstance(all);
}
}

View File

@@ -0,0 +1,15 @@
package sonia.scm.update;
import java.util.Map;
public class UserV1PropertyReader implements V1PropertyReader {
@Override
public String getStoreName() {
return "user-properties-v1";
}
@Override
public Instance createInstance(Map<String, V1Properties> all) {
return new MapBasedPropertyReaderInstance(all);
}
}

View File

@@ -0,0 +1,59 @@
package sonia.scm.update;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import static java.util.Arrays.asList;
import static java.util.Arrays.stream;
import static java.util.stream.Stream.empty;
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "properties")
public class V1Properties {
@XmlElement(name = "item")
private List<V1Property> properties;
public V1Properties() {
}
public V1Properties(V1Property... properties) {
this(asList(properties));
}
public V1Properties(List<V1Property> properties) {
this.properties = properties;
}
public String get(String key) {
return getOptional(key).orElse(null);
}
public Optional<String> getOptional(String key) {
return streamProps().filter(p -> key.equals(p.getKey())).map(V1Property::getValue).findFirst();
}
public Optional<Boolean> getBoolean(String key) {
return getOptional(key).map(Boolean::valueOf);
}
public <T extends Enum<T>> Optional<T> getEnum(String key, Class<T> enumType) {
return getOptional(key).map(name -> Enum.valueOf(enumType, name));
}
public boolean hasAny(String[] keys) {
return streamProps().anyMatch(p -> stream(keys).anyMatch(k -> k.equals(p.getKey())));
}
public boolean hasAll(String[] keys) {
return stream(keys).allMatch(k -> streamProps().anyMatch(p -> k.equals(p.getKey())));
}
private Stream<V1Property> streamProps() {
return properties == null? empty(): properties.stream();
}
}

View File

@@ -0,0 +1,49 @@
package sonia.scm.update;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import java.util.Objects;
@XmlAccessorType(XmlAccessType.FIELD)
public class V1Property {
private String key;
private String value;
public V1Property() {
}
public V1Property(String key, String value) {
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public String getValue() {
return value;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
V1Property that = (V1Property) o;
return Objects.equals(key, that.key) &&
Objects.equals(value, that.value);
}
@Override
public int hashCode() {
return Objects.hash(key, value);
}
@Override
public String toString() {
return "V1Property{" +
"key='" + key + '\'' +
", value='" + value + '\'' +
'}';
}
}

View File

@@ -0,0 +1,14 @@
package sonia.scm.update;
/**
* Use this to access old properties from an instance of SCM-Manager v1.
*/
public interface V1PropertyDAO {
/**
* Creates an instance of a property reader to process old properties.
* @param reader The reader for the origin of the properties (for example
* {@link V1PropertyReader#REPOSITORY_PROPERTY_READER} for properties of repositories).
* @return The reader instance.
*/
V1PropertyReader.Instance getProperties(V1PropertyReader reader);
}

View File

@@ -0,0 +1,35 @@
package sonia.scm.update;
import java.util.Map;
import java.util.function.BiConsumer;
public interface V1PropertyReader {
V1PropertyReader REPOSITORY_PROPERTY_READER = new RepositoryV1PropertyReader();
V1PropertyReader USER_PROPERTY_READER = new UserV1PropertyReader();
V1PropertyReader GROUP_PROPERTY_READER = new GroupV1PropertyReader();
String getStoreName();
Instance createInstance(Map<String, V1Properties> all);
interface Instance {
/**
* Will call the given consumer for each id of the corresponding entity with its list of
* properties converted from v1.
* For example for repositories this will call the consumer with the id of each repository
* that had properties attached in v1.
*/
void forEachEntry(BiConsumer<String, V1Properties> propertiesForNameConsumer);
/**
* Filters for entities only having at least one property with a given key name.
*/
Instance havingAnyOf(String... keys);
/**
* Filters for entities only having properties for all given key name.
*/
Instance havingAllOf(String... keys);
}
}

View File

@@ -0,0 +1,61 @@
package sonia.scm.update;
import com.google.common.collect.ImmutableMap;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
class MapBasedPropertyReaderInstanceTest {
Map<String, V1Properties> executedCalls = new HashMap<>();
BiConsumer<String, V1Properties> consumer = (key, properties) -> executedCalls.put(key, properties);
MapBasedPropertyReaderInstance instance = new MapBasedPropertyReaderInstance(
ImmutableMap.of(
"o1", new V1Properties(
new V1Property("k1", "v1-1"),
new V1Property("k2", "v1-2"),
new V1Property("k3", "v1-3")
),
"o2", new V1Properties(
new V1Property("k1", "v2-1"),
new V1Property("k2", "v2-2")
),
"o3", new V1Properties(
new V1Property("k1", "v3-1")
)
)
);
@Test
void shouldCallBackForEachObjectIfNotFiltered() {
instance.forEachEntry(consumer);
Assertions.assertThat(executedCalls).hasSize(3);
}
@Test
void shouldCallBackOnlyObjectsHavingAtLeastOneOfGivenKey() {
instance.havingAnyOf("k2", "k3").forEachEntry(consumer);
Assertions.assertThat(executedCalls).hasSize(2).containsKeys("o1", "o2");
}
@Test
void shouldCallBackOnlyObjectsHavingAllOfGivenKey() {
instance.havingAllOf("k2", "k3").forEachEntry(consumer);
Assertions.assertThat(executedCalls).hasSize(1).containsKeys("o1");
}
@Test
void shouldCombineFilters() {
instance.havingAnyOf("k2", "k3").havingAllOf("k3").forEachEntry(consumer);
Assertions.assertThat(executedCalls).hasSize(1).containsKeys("o1");
}
}

View File

@@ -35,7 +35,6 @@ package sonia.scm.store;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.SCMContextProvider;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryLocationResolver;
import sonia.scm.util.IOUtil;
@@ -66,18 +65,18 @@ public abstract class FileBasedStoreFactory {
}
protected File getStoreLocation(StoreParameters storeParameters) {
return getStoreLocation(storeParameters.getName(), null, storeParameters.getRepository());
return getStoreLocation(storeParameters.getName(), null, storeParameters.getRepositoryId());
}
protected File getStoreLocation(TypedStoreParameters storeParameters) {
return getStoreLocation(storeParameters.getName(), storeParameters.getType(), storeParameters.getRepository());
return getStoreLocation(storeParameters.getName(), storeParameters.getType(), storeParameters.getRepositoryId());
}
protected File getStoreLocation(String name, Class type, Repository repository) {
protected File getStoreLocation(String name, Class type, String repositoryId) {
File storeDirectory;
if (repository != null) {
LOG.debug("create store with type: {}, name: {} and repository: {}", type, name, repository.getNamespaceAndName());
storeDirectory = this.getStoreDirectory(store, repository);
if (repositoryId != null) {
LOG.debug("create store with type: {}, name: {} and repository id: {}", type, name, repositoryId);
storeDirectory = this.getStoreDirectory(store, repositoryId);
} else {
LOG.debug("create store with type: {} and name: {} ", type, name);
storeDirectory = this.getStoreDirectory(store);
@@ -89,11 +88,11 @@ public abstract class FileBasedStoreFactory {
/**
* Get the store directory of a specific repository
* @param store the type of the store
* @param repository the repo
* @param repositoryId the id of the repossitory
* @return the store directory of a specific repository
*/
private File getStoreDirectory(Store store, Repository repository) {
return new File(repositoryLocationResolver.forClass(Path.class).getLocation(repository.getId()).toFile(), store.getRepositoryStoreDirectory());
private File getStoreDirectory(Store store, String repositoryId) {
return new File(repositoryLocationResolver.forClass(Path.class).getLocation(repositoryId).toFile(), store.getRepositoryStoreDirectory());
}
/**

View File

@@ -59,7 +59,9 @@ public class JAXBConfigurationEntryStoreFactory extends FileBasedStoreFactory
@Override
public <T> ConfigurationEntryStore<T> getStore(TypedStoreParameters<T> storeParameters) {
return new JAXBConfigurationEntryStore<>(getStoreLocation(storeParameters.getName().concat(StoreConstants.FILE_EXTENSION), storeParameters.getType(), storeParameters.getRepository()), keyGenerator, storeParameters.getType());
return new JAXBConfigurationEntryStore<>(
getStoreLocation(storeParameters.getName().concat(StoreConstants.FILE_EXTENSION), storeParameters.getType(), storeParameters.getRepositoryId()),
keyGenerator,
storeParameters.getType());
}
}

View File

@@ -55,6 +55,10 @@ public class JAXBConfigurationStoreFactory extends FileBasedStoreFactory impleme
@Override
public <T> JAXBConfigurationStore<T> getStore(TypedStoreParameters<T> storeParameters) {
return new JAXBConfigurationStore<>(storeParameters.getType(), getStoreLocation(storeParameters.getName().concat(StoreConstants.FILE_EXTENSION), storeParameters.getType(), storeParameters.getRepository()));
return new JAXBConfigurationStore<>(
storeParameters.getType(),
getStoreLocation(storeParameters.getName().concat(StoreConstants.FILE_EXTENSION),
storeParameters.getType(),
storeParameters.getRepositoryId()));
}
}

View File

@@ -0,0 +1,91 @@
package sonia.scm.store;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.SCMContextProvider;
import sonia.scm.repository.RepositoryLocationResolver;
import sonia.scm.update.PropertyFileAccess;
import sonia.scm.util.IOUtil;
import javax.inject.Inject;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
public class JAXBPropertyFileAccess implements PropertyFileAccess {
private static final Logger LOG = LoggerFactory.getLogger(JAXBPropertyFileAccess.class);
public static final String XML_FILENAME_SUFFIX = ".xml";
private final SCMContextProvider contextProvider;
private final RepositoryLocationResolver locationResolver;
@Inject
public JAXBPropertyFileAccess(SCMContextProvider contextProvider, RepositoryLocationResolver locationResolver) {
this.contextProvider = contextProvider;
this.locationResolver = locationResolver;
}
@Override
public Target renameGlobalConfigurationFrom(String oldName) {
return newName -> {
Path configDir = contextProvider.getBaseDirectory().toPath().resolve(StoreConstants.CONFIG_DIRECTORY_NAME);
Path oldConfigFile = configDir.resolve(oldName + XML_FILENAME_SUFFIX);
Path newConfigFile = configDir.resolve(newName + XML_FILENAME_SUFFIX);
Files.move(oldConfigFile, newConfigFile);
};
}
@Override
public StoreFileTools forStoreName(String storeName) {
return new StoreFileTools() {
@Override
public void forStoreFiles(FileConsumer storeFileConsumer) throws IOException {
Path v1storeDir = computeV1StoreDir();
if (Files.exists(v1storeDir) && Files.isDirectory(v1storeDir)) {
try (Stream<Path> fileStream = Files.list(v1storeDir)) {
fileStream.filter(p -> p.toString().endsWith(XML_FILENAME_SUFFIX)).forEach(p -> {
try {
String storeName = extractStoreName(p);
storeFileConsumer.accept(p, storeName);
} catch (IOException e) {
throw new RuntimeException("could not call consumer for store file " + p + " with name " + storeName, e);
}
});
}
}
}
@Override
public void moveAsRepositoryStore(Path storeFile, String repositoryId) throws IOException {
Path repositoryLocation;
try {
repositoryLocation = locationResolver
.forClass(Path.class)
.getLocation(repositoryId);
} catch (IllegalStateException e) {
LOG.info("ignoring store file {} because there is no repository location for repository id {}", storeFile, repositoryId);
return;
}
Path target = repositoryLocation
.resolve(Store.DATA.getRepositoryStoreDirectory())
.resolve(storeName);
IOUtil.mkdirs(target.toFile());
Path resolvedSourceFile = computeV1StoreDir().resolve(storeFile);
Path resolvedTargetFile = target.resolve(storeFile.getFileName());
LOG.trace("moving file {} to {}", resolvedSourceFile, resolvedTargetFile);
Files.move(resolvedSourceFile, resolvedTargetFile);
}
private Path computeV1StoreDir() {
return contextProvider.getBaseDirectory().toPath().resolve("var").resolve("data").resolve(storeName);
}
private String extractStoreName(Path p) {
String fileName = p.getFileName().toString();
return fileName.substring(0, fileName.length() - XML_FILENAME_SUFFIX.length());
}
};
}
}

View File

@@ -0,0 +1,30 @@
package sonia.scm.update.xml;
import sonia.scm.store.ConfigurationEntryStore;
import sonia.scm.store.ConfigurationEntryStoreFactory;
import sonia.scm.update.V1Properties;
import sonia.scm.update.V1PropertyDAO;
import sonia.scm.update.V1PropertyReader;
import javax.inject.Inject;
import java.util.Map;
public class XmlV1PropertyDAO implements V1PropertyDAO {
private final ConfigurationEntryStoreFactory configurationEntryStoreFactory;
@Inject
public XmlV1PropertyDAO(ConfigurationEntryStoreFactory configurationEntryStoreFactory) {
this.configurationEntryStoreFactory = configurationEntryStoreFactory;
}
@Override
public V1PropertyReader.Instance getProperties(V1PropertyReader reader) {
ConfigurationEntryStore<V1Properties> propertyStore = configurationEntryStoreFactory
.withType(V1Properties.class)
.withName(reader.getStoreName())
.build();
Map<String, V1Properties> all = propertyStore.getAll();
return reader.createInstance(all);
}
}

View File

@@ -0,0 +1,119 @@
package sonia.scm.store;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junitpioneer.jupiter.TempDirectory;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.SCMContextProvider;
import sonia.scm.io.DefaultFileSystem;
import sonia.scm.repository.InitialRepositoryLocationResolver;
import sonia.scm.repository.RepositoryLocationResolver;
import sonia.scm.repository.xml.PathBasedRepositoryLocationResolver;
import sonia.scm.update.PropertyFileAccess;
import sonia.scm.util.IOUtil;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.lenient;
@ExtendWith(TempDirectory.class)
@ExtendWith(MockitoExtension.class)
class JAXBPropertyFileAccessTest {
public static final String REPOSITORY_ID = "repoId";
public static final String STORE_NAME = "test";
@Mock
SCMContextProvider contextProvider;
RepositoryLocationResolver locationResolver;
JAXBPropertyFileAccess fileAccess;
@BeforeEach
void initTempDir(@TempDirectory.TempDir Path tempDir) {
lenient().when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile());
lenient().when(contextProvider.resolve(any())).thenAnswer(invocation -> tempDir.resolve(invocation.getArgument(0).toString()));
locationResolver = new PathBasedRepositoryLocationResolver(contextProvider, new InitialRepositoryLocationResolver(), new DefaultFileSystem());
fileAccess = new JAXBPropertyFileAccess(contextProvider, locationResolver);
}
@Test
void shouldRenameGlobalConfigFile() throws IOException {
Path baseDirectory = contextProvider.getBaseDirectory().toPath();
Path configDirectory = baseDirectory.resolve(StoreConstants.CONFIG_DIRECTORY_NAME);
Files.createDirectories(configDirectory);
Path oldPath = configDirectory.resolve("old" + StoreConstants.FILE_EXTENSION);
Files.createFile(oldPath);
fileAccess.renameGlobalConfigurationFrom("old").to("new");
Path newPath = configDirectory.resolve("new" + StoreConstants.FILE_EXTENSION);
assertThat(oldPath).doesNotExist();
assertThat(newPath).exists();
}
@Nested
class ForExistingRepository {
@BeforeEach
void createRepositoryLocation() {
locationResolver.forClass(Path.class).createLocation(REPOSITORY_ID);
}
@Test
void shouldMoveStoreFileToRepositoryBasedLocation(@TempDirectory.TempDir Path tempDir) throws IOException {
createV1StoreFile(tempDir, "myStore.xml");
fileAccess.forStoreName(STORE_NAME).moveAsRepositoryStore(Paths.get("myStore.xml"), REPOSITORY_ID);
assertThat(tempDir.resolve("repositories").resolve(REPOSITORY_ID).resolve("store").resolve("data").resolve(STORE_NAME).resolve("myStore.xml")).exists();
}
@Test
void shouldMoveAllStoreFilesToRepositoryBasedLocations(@TempDirectory.TempDir Path tempDir) throws IOException {
locationResolver.forClass(Path.class).createLocation("repoId2");
createV1StoreFile(tempDir, REPOSITORY_ID + ".xml");
createV1StoreFile(tempDir, "repoId2.xml");
PropertyFileAccess.StoreFileTools statisticStoreAccess = fileAccess.forStoreName(STORE_NAME);
statisticStoreAccess.forStoreFiles(statisticStoreAccess::moveAsRepositoryStore);
assertThat(tempDir.resolve("repositories").resolve(REPOSITORY_ID).resolve("store").resolve("data").resolve(STORE_NAME).resolve("repoId.xml")).exists();
assertThat(tempDir.resolve("repositories").resolve("repoId2").resolve("store").resolve("data").resolve(STORE_NAME).resolve("repoId2.xml")).exists();
}
}
private void createV1StoreFile(@TempDirectory.TempDir Path tempDir, String name) throws IOException {
Path v1Dir = tempDir.resolve("var").resolve("data").resolve(STORE_NAME);
IOUtil.mkdirs(v1Dir.toFile());
Files.createFile(v1Dir.resolve(name));
}
@Nested
class ForMissingRepository {
@Test
void shouldIgnoreStoreFile(@TempDirectory.TempDir Path tempDir) throws IOException {
createV1StoreFile(tempDir, "myStore.xml");
fileAccess.forStoreName(STORE_NAME).moveAsRepositoryStore(Paths.get("myStore.xml"), REPOSITORY_ID);
assertThat(tempDir.resolve("repositories").resolve(REPOSITORY_ID).resolve("store").resolve("data").resolve(STORE_NAME).resolve("myStore.xml")).doesNotExist();
}
}
}

View File

@@ -40,6 +40,7 @@ import org.junit.Ignore;
import org.junit.Test;
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
import sonia.scm.repository.ChangesetPagingResult;
import sonia.scm.repository.Repository;
import sonia.scm.store.InMemoryConfigurationStoreFactory;
import java.io.IOException;
@@ -189,7 +190,7 @@ public class GitIncomingCommandTest
*/
private GitIncomingCommand createCommand()
{
return new GitIncomingCommand(handler, new GitContext(incomingDirectory, null, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory())),
incomingRepository);
return new GitIncomingCommand(handler, new GitContext(incomingDirectory, incomingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory())),
this.incomingRepository);
}
}

View File

@@ -40,6 +40,7 @@ import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Test;
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
import sonia.scm.repository.ChangesetPagingResult;
import sonia.scm.repository.Repository;
import sonia.scm.store.InMemoryConfigurationStoreFactory;
import java.io.IOException;
@@ -160,7 +161,7 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase
*/
private GitOutgoingCommand createCommand()
{
return new GitOutgoingCommand(handler, new GitContext(outgoingDirectory, null, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory())),
return new GitOutgoingCommand(handler, new GitContext(outgoingDirectory, outgoingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory())),
outgoingRepository);
}
}

View File

@@ -71,7 +71,7 @@ public class LfsBlobStoreFactoryTest {
// the return value (and should not be part of this test)
verify(blobStoreFactory).getStore(argThat(blobStoreParameters -> {
assertThat(blobStoreParameters.getName()).isEqualTo("the-id-git-lfs");
assertThat(blobStoreParameters.getRepository()).isEqualTo(repository);
assertThat(blobStoreParameters.getRepositoryId()).isEqualTo("the-id");
return true;
}));

View File

@@ -1,28 +1,23 @@
package sonia.scm.store;
import java.util.HashMap;
import java.util.Map;
public class InMemoryConfigurationEntryStoreFactory implements ConfigurationEntryStoreFactory {
private final Map<String, InMemoryConfigurationEntryStore> stores = new HashMap<>();
private ConfigurationEntryStore store;
public static ConfigurationEntryStoreFactory create() {
return new InMemoryConfigurationEntryStoreFactory(new InMemoryConfigurationEntryStore());
}
public InMemoryConfigurationEntryStoreFactory() {
}
public InMemoryConfigurationEntryStoreFactory(ConfigurationEntryStore store) {
this.store = store;
public static InMemoryConfigurationEntryStoreFactory create() {
return new InMemoryConfigurationEntryStoreFactory();
}
@Override
public <T> ConfigurationEntryStore<T> getStore(TypedStoreParameters<T> storeParameters) {
if (store != null) {
return store;
String name = storeParameters.getName();
return get(name);
}
return new InMemoryConfigurationEntryStore<>();
public <T> InMemoryConfigurationEntryStore<T> get(String name) {
return stores.computeIfAbsent(name, x -> new InMemoryConfigurationEntryStore());
}
}

View File

@@ -35,6 +35,9 @@ package sonia.scm.store;
//~--- non-JDK imports --------------------------------------------------------
import java.util.HashMap;
import java.util.Map;
/**
* In memory configuration store factory for testing purposes.
*
@@ -44,24 +47,19 @@ package sonia.scm.store;
*/
public class InMemoryConfigurationStoreFactory implements ConfigurationStoreFactory {
private ConfigurationStore store;
private final Map<String, InMemoryConfigurationStore> stores = new HashMap<>();
public static ConfigurationStoreFactory create() {
return new InMemoryConfigurationStoreFactory(new InMemoryConfigurationStore());
}
public InMemoryConfigurationStoreFactory() {
}
public InMemoryConfigurationStoreFactory(ConfigurationStore store) {
this.store = store;
public static InMemoryConfigurationStoreFactory create() {
return new InMemoryConfigurationStoreFactory();
}
@Override
public ConfigurationStore getStore(TypedStoreParameters storeParameters) {
if (store != null) {
return store;
String name = storeParameters.getName();
return get(name);
}
return new InMemoryConfigurationStore<>();
public ConfigurationStore get(String name) {
return stores.computeIfAbsent(name, x -> new InMemoryConfigurationStore());
}
}

View File

@@ -3,11 +3,6 @@ package sonia.scm.update;
import com.google.common.io.Resources;
import org.mockito.Mockito;
import sonia.scm.SCMContextProvider;
import sonia.scm.security.AssignedPermission;
import sonia.scm.security.DefaultKeyGenerator;
import sonia.scm.store.ConfigurationEntryStore;
import sonia.scm.store.ConfigurationEntryStoreFactory;
import sonia.scm.store.JAXBConfigurationEntryStoreFactory;
import java.io.IOException;
import java.net.URL;
@@ -20,15 +15,13 @@ import static org.mockito.Mockito.lenient;
public class UpdateStepTestUtil {
private final SCMContextProvider contextProvider;
private final SCMContextProvider contextProvider;
private final Path tempDir;
private final ConfigurationEntryStoreFactory storeFactory;
public UpdateStepTestUtil(Path tempDir) {
this.tempDir = tempDir;
contextProvider = Mockito.mock(SCMContextProvider.class);
storeFactory = new JAXBConfigurationEntryStoreFactory(contextProvider, null, new DefaultKeyGenerator());
lenient().when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile());
lenient().when(contextProvider.resolve(any())).thenAnswer(invocation -> tempDir.resolve(invocation.getArgument(0).toString()));
}
@@ -37,10 +30,6 @@ private final SCMContextProvider contextProvider;
return contextProvider;
}
public ConfigurationEntryStoreFactory getStoreFactory() {
return storeFactory;
}
public void copyConfigFile(String fileName) throws IOException {
Path configDir = tempDir.resolve("config");
Files.createDirectories(configDir);
@@ -53,17 +42,6 @@ private final SCMContextProvider contextProvider;
copyTestDatabaseFile(configDir, fileName, targetFileName);
}
public ConfigurationEntryStore<AssignedPermission> getStoreForConfigFile(String name) {
return storeFactory
.withType(AssignedPermission.class)
.withName(name)
.build();
}
public Path getFile(String name) {
return tempDir.resolve("config").resolve(name);
}
private void copyTestDatabaseFile(Path configDir, String fileName) throws IOException {
Path targetFileName = Paths.get(fileName).getFileName();
copyTestDatabaseFile(configDir, fileName, targetFileName.toString());

View File

@@ -0,0 +1,40 @@
package sonia.scm.update;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import static java.util.Arrays.stream;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class V1PropertyDaoTestUtil {
private final V1PropertyDAO propertyDAO = mock(V1PropertyDAO.class);
public V1PropertyDAO getPropertyDAO() {
return propertyDAO;
}
public void mockRepositoryProperties(PropertiesForRepository... mockedPropertiesForRepositories) {
Map<String, V1Properties> map = new HashMap<>();
stream(mockedPropertiesForRepositories).forEach(p -> map.put(p.repositoryId, p.asProperties()));
V1PropertyReader.Instance v1PropertyReader = new MapBasedPropertyReaderInstance(map);
when(propertyDAO.getProperties(argThat(argument -> argument instanceof RepositoryV1PropertyReader))).thenReturn(v1PropertyReader);
}
public static class PropertiesForRepository {
private final String repositoryId;
private final Map<String, String> properties;
public PropertiesForRepository(String repositoryId, Map<String, String> properties) {
this.repositoryId = repositoryId;
this.properties = properties;
}
V1Properties asProperties() {
return new V1Properties(properties.entrySet().stream().map(e -> new V1Property(e.getKey(), e.getValue())).collect(Collectors.toList()));
}
}
}

View File

@@ -24,6 +24,10 @@ import sonia.scm.store.FileBlobStoreFactory;
import sonia.scm.store.JAXBConfigurationEntryStoreFactory;
import sonia.scm.store.JAXBConfigurationStoreFactory;
import sonia.scm.store.JAXBDataStoreFactory;
import sonia.scm.store.JAXBPropertyFileAccess;
import sonia.scm.update.PropertyFileAccess;
import sonia.scm.update.V1PropertyDAO;
import sonia.scm.update.xml.XmlV1PropertyDAO;
public class BootstrapModule extends AbstractModule {
@@ -60,6 +64,8 @@ public class BootstrapModule extends AbstractModule {
bind(DataStoreFactory.class, JAXBDataStoreFactory.class);
bind(BlobStoreFactory.class, FileBlobStoreFactory.class);
bind(PluginLoader.class).toInstance(pluginLoader);
bind(V1PropertyDAO.class, XmlV1PropertyDAO.class);
bind(PropertyFileAccess.class, JAXBPropertyFileAccess.class);
}
private <T> void bind(Class<T> clazz, Class<? extends T> defaultImplementation) {

View File

@@ -0,0 +1,6 @@
package sonia.scm.update;
import sonia.scm.migration.UpdateStep;
public interface CoreUpdateStep extends UpdateStep {
}

View File

@@ -32,12 +32,20 @@ public class UpdateEngine {
private List<UpdateStep> sortSteps(Set<UpdateStep> steps) {
LOG.trace("sorting available update steps:");
List<UpdateStep> sortedSteps = steps.stream()
.sorted(Comparator.comparing(UpdateStep::getTargetVersion).reversed())
.sorted(
Comparator
.comparing(UpdateStep::getTargetVersion)
.thenComparing(this::isCoreUpdateStep)
.reversed())
.collect(toList());
sortedSteps.forEach(step -> LOG.trace("{} for version {}", step.getAffectedDataType(), step.getTargetVersion()));
return sortedSteps;
}
private boolean isCoreUpdateStep(UpdateStep updateStep) {
return updateStep instanceof CoreUpdateStep;
}
public void update() {
steps
.stream()

View File

@@ -11,7 +11,7 @@ import sonia.scm.plugin.Extension;
import sonia.scm.store.ConfigurationEntryStore;
import sonia.scm.store.ConfigurationEntryStoreFactory;
import sonia.scm.store.StoreConstants;
import sonia.scm.update.properties.V1Properties;
import sonia.scm.update.V1Properties;
import sonia.scm.version.Version;
import javax.inject.Inject;
@@ -30,6 +30,7 @@ import java.util.Optional;
import static java.util.Optional.empty;
import static java.util.Optional.of;
import static sonia.scm.update.V1PropertyReader.GROUP_PROPERTY_READER;
import static sonia.scm.version.Version.parse;
@Extension
@@ -51,7 +52,7 @@ public class XmlGroupV1UpdateStep implements UpdateStep {
this.groupDAO = groupDAO;
this.propertyStore = configurationEntryStoreFactory
.withType(V1Properties.class)
.withName("group-properties-v1")
.withName(GROUP_PROPERTY_READER.getStoreName())
.build();
}

View File

@@ -1,14 +0,0 @@
package sonia.scm.update.properties;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.List;
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "properties")
public class V1Properties {
@XmlElement(name = "item")
private List<V1Property> properties;
}

View File

@@ -1,10 +0,0 @@
package sonia.scm.update.properties;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
@XmlAccessorType(XmlAccessType.FIELD)
public class V1Property {
private String key;
private String value;
}

View File

@@ -1,6 +1,6 @@
package sonia.scm.update.repository;
import sonia.scm.update.properties.V1Properties;
import sonia.scm.update.V1Properties;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;

View File

@@ -5,7 +5,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.SCMContextProvider;
import sonia.scm.migration.UpdateException;
import sonia.scm.migration.UpdateStep;
import sonia.scm.plugin.Extension;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryPermission;
@@ -13,7 +12,8 @@ import sonia.scm.repository.xml.XmlRepositoryDAO;
import sonia.scm.store.ConfigurationEntryStore;
import sonia.scm.store.ConfigurationEntryStoreFactory;
import sonia.scm.store.StoreConstants;
import sonia.scm.update.properties.V1Properties;
import sonia.scm.update.CoreUpdateStep;
import sonia.scm.update.V1Properties;
import sonia.scm.version.Version;
import javax.inject.Inject;
@@ -36,6 +36,7 @@ import java.util.stream.Stream;
import static java.util.Collections.emptyList;
import static java.util.Optional.empty;
import static java.util.Optional.of;
import static sonia.scm.update.V1PropertyReader.REPOSITORY_PROPERTY_READER;
import static sonia.scm.version.Version.parse;
/**
@@ -56,7 +57,7 @@ import static sonia.scm.version.Version.parse;
* </ul>
*/
@Extension
public class XmlRepositoryV1UpdateStep implements UpdateStep {
public class XmlRepositoryV1UpdateStep implements CoreUpdateStep {
private static Logger LOG = LoggerFactory.getLogger(XmlRepositoryV1UpdateStep.class);
@@ -80,7 +81,7 @@ public class XmlRepositoryV1UpdateStep implements UpdateStep {
this.injector = injector;
this.propertyStore = configurationEntryStoreFactory
.withType(V1Properties.class)
.withName("repository-properties-v1")
.withName(REPOSITORY_PROPERTY_READER.getStoreName())
.build();
}

View File

@@ -10,7 +10,7 @@ import sonia.scm.security.AssignedPermission;
import sonia.scm.store.ConfigurationEntryStore;
import sonia.scm.store.ConfigurationEntryStoreFactory;
import sonia.scm.store.StoreConstants;
import sonia.scm.update.properties.V1Properties;
import sonia.scm.update.V1Properties;
import sonia.scm.user.User;
import sonia.scm.user.xml.XmlUserDAO;
import sonia.scm.version.Version;
@@ -31,6 +31,7 @@ import java.util.Optional;
import static java.util.Optional.empty;
import static java.util.Optional.of;
import static sonia.scm.update.V1PropertyReader.USER_PROPERTY_READER;
import static sonia.scm.version.Version.parse;
@Extension
@@ -50,7 +51,7 @@ public class XmlUserV1UpdateStep implements UpdateStep {
this.configurationEntryStoreFactory = configurationEntryStoreFactory;
this.propertyStore = configurationEntryStoreFactory
.withType(V1Properties.class)
.withName("user-properties-v1")
.withName(USER_PROPERTY_READER.getStoreName())
.build();
}

View File

@@ -16,7 +16,7 @@ import static sonia.scm.version.Version.parse;
class UpdateEngineTest {
ConfigurationEntryStoreFactory storeFactory = new InMemoryConfigurationEntryStoreFactory(new InMemoryConfigurationEntryStore());
ConfigurationEntryStoreFactory storeFactory = new InMemoryConfigurationEntryStoreFactory();
List<String> processedUpdates = new ArrayList<>();
@@ -32,7 +32,21 @@ class UpdateEngineTest {
updateEngine.update();
assertThat(processedUpdates)
.containsExactly("1.1.0", "1.1.1", "1.2.0");
.containsExactly("test:1.1.0", "test:1.1.1", "test:1.2.0");
}
@Test
void shouldProcessCoreStepsBeforeOther() {
LinkedHashSet<UpdateStep> updateSteps = new LinkedHashSet<>();
updateSteps.add(new FixedVersionUpdateStep("test", "1.2.0"));
updateSteps.add(new CoreFixedVersionUpdateStep("core", "1.2.0"));
UpdateEngine updateEngine = new UpdateEngine(updateSteps, storeFactory);
updateEngine.update();
assertThat(processedUpdates)
.containsExactly("core:1.2.0", "test:1.2.0");
}
@Test
@@ -67,7 +81,7 @@ class UpdateEngineTest {
updateEngine = new UpdateEngine(updateSteps, storeFactory);
updateEngine.update();
assertThat(processedUpdates).containsExactly("1.1.1");
assertThat(processedUpdates).containsExactly("other:1.1.1");
}
class FixedVersionUpdateStep implements UpdateStep {
@@ -91,7 +105,13 @@ class UpdateEngineTest {
@Override
public void doUpdate() {
processedUpdates.add(version);
processedUpdates.add(type + ":" + version);
}
}
class CoreFixedVersionUpdateStep extends FixedVersionUpdateStep implements CoreUpdateStep {
CoreFixedVersionUpdateStep(String type, String version) {
super(type, version);
}
}
}

View File

@@ -11,7 +11,11 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.group.Group;
import sonia.scm.group.xml.XmlGroupDAO;
import sonia.scm.store.ConfigurationEntryStore;
import sonia.scm.store.InMemoryConfigurationEntryStoreFactory;
import sonia.scm.update.UpdateStepTestUtil;
import sonia.scm.update.V1Properties;
import sonia.scm.update.V1Property;
import javax.xml.bind.JAXBException;
import java.io.IOException;
@@ -20,11 +24,11 @@ import java.util.Optional;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.linesOf;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static sonia.scm.store.InMemoryConfigurationEntryStoreFactory.create;
@ExtendWith(MockitoExtension.class)
@ExtendWith(TempDirectory.class)
@@ -36,6 +40,8 @@ class XmlGroupV1UpdateStepTest {
@Captor
ArgumentCaptor<Group> groupCaptor;
InMemoryConfigurationEntryStoreFactory storeFactory = create();
XmlGroupV1UpdateStep updateStep;
private UpdateStepTestUtil testUtil;
@@ -44,7 +50,7 @@ class XmlGroupV1UpdateStepTest {
@BeforeEach
void mockScmHome(@TempDirectory.TempDir Path tempDir) {
testUtil = new UpdateStepTestUtil(tempDir);
updateStep = new XmlGroupV1UpdateStep(testUtil.getContextProvider(), groupDAO, testUtil.getStoreFactory());
updateStep = new XmlGroupV1UpdateStep(testUtil.getContextProvider(), groupDAO, storeFactory);
}
@Nested
@@ -83,19 +89,10 @@ class XmlGroupV1UpdateStepTest {
@Test
void shouldExtractProperties() throws JAXBException {
updateStep.doUpdate();
Path propertiesFile = testUtil.getFile("group-properties-v1.xml");
assertThat(propertiesFile)
.exists();
assertThat(linesOf(propertiesFile.toFile()))
.extracting(String::trim)
.containsSequence(
"<key>normals</key>",
"<value>",
"<item>",
"<key>mostly</key>",
"<value>humans</value>",
"</item>",
"</value>");
ConfigurationEntryStore<V1Properties> propertiesStore = storeFactory.get("group-properties-v1");
V1Properties properties = propertiesStore.get("normals");
assertThat(properties).isNotNull();
assertThat(properties.get("mostly")).isEqualTo("humans");
}
}

View File

@@ -51,7 +51,7 @@ class XmlRepositoryV1UpdateStepTest {
@Mock
MigrationStrategyDao migrationStrategyDao;
ConfigurationEntryStoreFactory configurationEntryStoreFactory = new InMemoryConfigurationEntryStoreFactory(new InMemoryConfigurationEntryStore());
InMemoryConfigurationEntryStoreFactory configurationEntryStoreFactory = new InMemoryConfigurationEntryStoreFactory();
@Captor
ArgumentCaptor<Repository> storeCaptor;
@@ -137,7 +137,7 @@ class XmlRepositoryV1UpdateStepTest {
void shouldExtractPropertiesFromRepositories() throws JAXBException {
updateStep.doUpdate();
ConfigurationEntryStore<Object> store = configurationEntryStoreFactory.withType(null).withName("").build();
ConfigurationEntryStore store = configurationEntryStoreFactory.get("repository-properties-v1");
assertThat(store.getAll())
.hasSize(3);
}

View File

@@ -26,6 +26,7 @@ import java.util.List;
import static java.util.stream.Collectors.toList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import static sonia.scm.store.InMemoryConfigurationEntryStoreFactory.create;
@ExtendWith(MockitoExtension.class)
@ExtendWith(TempDirectory.class)
@@ -40,8 +41,8 @@ class XmlSecurityV1UpdateStepTest {
@BeforeEach
void mockScmHome(@TempDirectory.TempDir Path tempDir) {
when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile());
assignedPermissionStore = new InMemoryConfigurationEntryStore<>();
ConfigurationEntryStoreFactory inMemoryConfigurationEntryStoreFactory = new InMemoryConfigurationEntryStoreFactory(assignedPermissionStore);
InMemoryConfigurationEntryStoreFactory inMemoryConfigurationEntryStoreFactory = create();
assignedPermissionStore = inMemoryConfigurationEntryStoreFactory.get("security");
updateStep = new XmlSecurityV1UpdateStep(contextProvider, inMemoryConfigurationEntryStoreFactory);
}

View File

@@ -10,7 +10,11 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.security.AssignedPermission;
import sonia.scm.store.ConfigurationEntryStore;
import sonia.scm.store.InMemoryConfigurationEntryStoreFactory;
import sonia.scm.update.UpdateStepTestUtil;
import sonia.scm.update.V1Properties;
import sonia.scm.update.V1Property;
import sonia.scm.user.User;
import sonia.scm.user.xml.XmlUserDAO;
@@ -20,11 +24,11 @@ import java.nio.file.Path;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.linesOf;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static sonia.scm.store.InMemoryConfigurationEntryStoreFactory.create;
@ExtendWith(MockitoExtension.class)
@ExtendWith(TempDirectory.class)
@@ -36,6 +40,8 @@ class XmlUserV1UpdateStepTest {
@Captor
ArgumentCaptor<User> userCaptor;
InMemoryConfigurationEntryStoreFactory storeFactory = create();
XmlUserV1UpdateStep updateStep;
private UpdateStepTestUtil testUtil;
@@ -43,7 +49,7 @@ class XmlUserV1UpdateStepTest {
@BeforeEach
void mockScmHome(@TempDirectory.TempDir Path tempDir) {
testUtil = new UpdateStepTestUtil(tempDir);
updateStep = new XmlUserV1UpdateStep(testUtil.getContextProvider(), userDAO, testUtil.getStoreFactory());
updateStep = new XmlUserV1UpdateStep(testUtil.getContextProvider(), userDAO, storeFactory);
}
@Nested
@@ -63,7 +69,7 @@ class XmlUserV1UpdateStepTest {
void shouldCreateNewPermissionsForV1AdminUser() throws JAXBException {
updateStep.doUpdate();
Optional<AssignedPermission> assignedPermission =
testUtil.getStoreForConfigFile("security")
storeFactory.<AssignedPermission>get("security")
.getAll()
.values()
.stream()
@@ -98,23 +104,11 @@ class XmlUserV1UpdateStepTest {
@Test
void shouldExtractProperties() throws JAXBException {
updateStep.doUpdate();
Path propertiesFile = testUtil.getFile("user-properties-v1.xml");
assertThat(propertiesFile)
.exists();
assertThat(linesOf(propertiesFile.toFile()))
.extracting(String::trim)
.containsSequence(
"<key>dent</key>",
"<value>",
"<item>",
"<key>born.on</key>",
"<value>earth</value>",
"</item>",
"<item>",
"<key>last.seen</key>",
"<value>end of the universe</value>",
"</item>",
"</value>");
ConfigurationEntryStore<V1Properties> propertiesStore = storeFactory.<V1Properties>get("user-properties-v1");
V1Properties properties = propertiesStore.get("dent");
assertThat(properties).isNotNull();
assertThat(properties.get("born.on")).isEqualTo("earth");
assertThat(properties.get("last.seen")).isEqualTo("end of the universe");
}
}