From e9401624a7638f005ce4c3002c3f180ec299b15e Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 28 Nov 2018 19:49:55 +0100 Subject: [PATCH] re implement XmlRepositoryDAO --- pom.xml | 24 ++ .../java/sonia/scm/BasicContextProvider.java | 20 + .../java/sonia/scm/SCMContextProvider.java | 12 + .../AbstractSimpleRepositoryHandler.java | 2 +- .../InitialRepositoryLocationResolver.java | 44 +- .../RepositoryLocationResolver.java | 29 +- .../sonia/scm/xml/IndentXMLStreamWriter.java | 4 +- .../sonia/scm/BasicContextProviderTest.java | 44 ++ ...InitialRepositoryLocationResolverTest.java | 39 +- .../RepositoryLocationResolverTest.java | 65 +++ .../scm/xml/IndentXMLStreamWriterTest.java | 2 +- .../scm/repository/xml/MetadataStore.java | 50 +++ .../scm/repository/xml/PathDatabase.java | 145 +++++++ .../scm/repository/xml/XmlRepositoryDAO.java | 253 +++++++---- .../store/JAXBConfigurationEntryStore.java | 120 +----- .../main/java/sonia/scm/xml/XmlStreams.java | 71 ++++ .../repository/xml/XmlRepositoryDAOTest.java | 394 ++++++++++++++---- .../repository/GitRepositoryHandlerTest.java | 8 +- .../repository/HgRepositoryHandlerTest.java | 14 +- .../java/sonia/scm/repository/HgTestUtil.java | 2 +- .../repository/TempSCMContextProvider.java | 6 + .../repository/SvnRepositoryHandlerTest.java | 11 +- .../scm/repository/RepositoryTestData.java | 4 + .../SimpleRepositoryHandlerTestBase.java | 14 +- .../main/java/sonia/scm/util/MockUtil.java | 5 + .../DefaultRepositoryManagerTest.java | 6 +- 26 files changed, 1019 insertions(+), 369 deletions(-) create mode 100644 scm-core/src/test/java/sonia/scm/BasicContextProviderTest.java create mode 100644 scm-core/src/test/java/sonia/scm/repository/RepositoryLocationResolverTest.java create mode 100644 scm-dao-xml/src/main/java/sonia/scm/repository/xml/MetadataStore.java create mode 100644 scm-dao-xml/src/main/java/sonia/scm/repository/xml/PathDatabase.java create mode 100644 scm-dao-xml/src/main/java/sonia/scm/xml/XmlStreams.java diff --git a/pom.xml b/pom.xml index e480d1527b..f8bb7c5727 100644 --- a/pom.xml +++ b/pom.xml @@ -142,6 +142,11 @@ junit-vintage-engine + + org.junit-pioneer + junit-pioneer + + org.hamcrest hamcrest-core @@ -159,6 +164,11 @@ mockito-core + + org.mockito + mockito-junit-jupiter + + org.assertj assertj-core @@ -325,6 +335,13 @@ test + + org.junit-pioneer + junit-pioneer + 0.3.0 + test + + org.hamcrest hamcrest-core @@ -346,6 +363,13 @@ test + + org.mockito + mockito-junit-jupiter + ${mockito.version} + test + + org.assertj assertj-core diff --git a/scm-core/src/main/java/sonia/scm/BasicContextProvider.java b/scm-core/src/main/java/sonia/scm/BasicContextProvider.java index f6507fc453..6954c03832 100644 --- a/scm-core/src/main/java/sonia/scm/BasicContextProvider.java +++ b/scm-core/src/main/java/sonia/scm/BasicContextProvider.java @@ -35,6 +35,7 @@ package sonia.scm; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.annotations.VisibleForTesting; import sonia.scm.util.Util; //~--- JDK imports ------------------------------------------------------------ @@ -43,6 +44,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.nio.file.Path; import java.util.Locale; import java.util.Properties; @@ -105,8 +107,26 @@ public class BasicContextProvider implements SCMContextProvider } } + @VisibleForTesting + BasicContextProvider(File baseDirectory, String version, Stage stage) { + this.baseDirectory = baseDirectory; + this.version = version; + this.stage = stage; + } + //~--- methods -------------------------------------------------------------- + + @Override + public Path resolve(Path path) { + if (path.isAbsolute()) { + return path; + } + + return baseDirectory.toPath().resolve(path); + } + + /** * {@inheritDoc} */ diff --git a/scm-core/src/main/java/sonia/scm/SCMContextProvider.java b/scm-core/src/main/java/sonia/scm/SCMContextProvider.java index 18328403fe..93918770c8 100644 --- a/scm-core/src/main/java/sonia/scm/SCMContextProvider.java +++ b/scm-core/src/main/java/sonia/scm/SCMContextProvider.java @@ -37,6 +37,7 @@ package sonia.scm; import java.io.Closeable; import java.io.File; +import java.nio.file.Path; /** * The main class for retrieving the home and the version of the SCM-Manager. @@ -65,6 +66,17 @@ public interface SCMContextProvider extends Closeable */ public File getBaseDirectory(); + /** + * Resolves the given path against the base directory. + * + * @param path path to resolve + * + * @return absolute resolved path + * + * @since 2.0.0 + */ + Path resolve(Path path); + /** * Returns the current stage of SCM-Manager. * diff --git a/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java b/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java index 17e5d3e24f..9941a4253b 100644 --- a/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java +++ b/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java @@ -168,6 +168,6 @@ public abstract class AbstractSimpleRepositoryHandler) invocationOnMock -> invocationOnMock.getArgument(0)); + } + + private RepositoryLocationResolver createResolver(RepositoryDAO pathBasedRepositoryDAO) { + return new RepositoryLocationResolver(contextProvider, pathBasedRepositoryDAO, initialRepositoryLocationResolver); + } + + @Test + void shouldReturnPathFromDao() { + Path repositoryPath = Paths.get("repos", "42"); + when(pathBasedRepositoryDAO.getPath("42")).thenReturn(repositoryPath); + + RepositoryLocationResolver resolver = createResolver(pathBasedRepositoryDAO); + Path path = resolver.getPath("42"); + + assertThat(path).isSameAs(repositoryPath); + } + + @Test + void shouldReturnInitialPathIfDaoIsNotPathBased() { + Path repositoryPath = Paths.get("r", "42"); + when(initialRepositoryLocationResolver.getPath("42")).thenReturn(repositoryPath); + + RepositoryLocationResolver resolver = createResolver(repositoryDAO); + Path path = resolver.getPath("42"); + + assertThat(path).isSameAs(repositoryPath); + } + +} diff --git a/scm-core/src/test/java/sonia/scm/xml/IndentXMLStreamWriterTest.java b/scm-core/src/test/java/sonia/scm/xml/IndentXMLStreamWriterTest.java index ecfdd06a0f..16c4278793 100644 --- a/scm-core/src/test/java/sonia/scm/xml/IndentXMLStreamWriterTest.java +++ b/scm-core/src/test/java/sonia/scm/xml/IndentXMLStreamWriterTest.java @@ -89,7 +89,7 @@ public class IndentXMLStreamWriterTest StringBuilder buffer = new StringBuilder(""); buffer.append(IndentXMLStreamWriter.LINE_SEPARATOR); buffer.append("").append(IndentXMLStreamWriter.LINE_SEPARATOR); - buffer.append(" Hello"); + buffer.append(" Hello"); buffer.append(IndentXMLStreamWriter.LINE_SEPARATOR); buffer.append("").append(IndentXMLStreamWriter.LINE_SEPARATOR); diff --git a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/MetadataStore.java b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/MetadataStore.java new file mode 100644 index 0000000000..1f5f0e81b6 --- /dev/null +++ b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/MetadataStore.java @@ -0,0 +1,50 @@ +package sonia.scm.repository.xml; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.ContextEntry; +import sonia.scm.repository.InternalRepositoryException; +import sonia.scm.repository.Repository; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import java.nio.file.Path; + +class MetadataStore { + + private static final Logger LOG = LoggerFactory.getLogger(MetadataStore.class); + + private final JAXBContext jaxbContext; + + MetadataStore() { + try { + jaxbContext = JAXBContext.newInstance(Repository.class); + } catch (JAXBException ex) { + throw new IllegalStateException("failed to create jaxb context for repository", ex); + } + } + + Repository read(Path path) { + LOG.trace("read repository metadata from {}", path); + try { + return (Repository) jaxbContext.createUnmarshaller().unmarshal(path.toFile()); + } catch (JAXBException ex) { + throw new InternalRepositoryException( + ContextEntry.ContextBuilder.entity(Path.class, path.toString()).build(), "failed read repository metadata", ex + ); + } + } + + void write(Path path, Repository repository) { + LOG.trace("write repository metadata of {} to {}", repository.getNamespaceAndName(), path); + try { + Marshaller marshaller = jaxbContext.createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); + marshaller.marshal(repository, path.toFile()); + } catch (JAXBException ex) { + throw new InternalRepositoryException(repository, "failed write repository metadata", ex); + } + } + +} diff --git a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/PathDatabase.java b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/PathDatabase.java new file mode 100644 index 0000000000..bddcdec570 --- /dev/null +++ b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/PathDatabase.java @@ -0,0 +1,145 @@ +package sonia.scm.repository.xml; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.ContextEntry; +import sonia.scm.repository.InternalRepositoryException; +import sonia.scm.xml.IndentXMLStreamWriter; +import sonia.scm.xml.XmlStreams; + +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; + +class PathDatabase { + + private static final Logger LOG = LoggerFactory.getLogger(PathDatabase.class); + + private static final String ENCODING = "UTF-8"; + private static final String VERSION = "1.0"; + + private static final String ELEMENT_REPOSITORIES = "repositories"; + private static final String ATTRIBUTE_CREATION_TIME = "creation-time"; + private static final String ATTRIBUTE_LAST_MODIFIED = "last-modified"; + + private static final String ELEMENT_REPOSITORY = "repository"; + private static final String ATTRIBUTE_ID = "id"; + + private final Path storePath; + + PathDatabase(Path storePath){ + this.storePath = storePath; + } + + void write(Long creationTime, Long lastModified, Map pathDatabase) { + ensureParentDirectoryExists(); + LOG.trace("write repository path database to {}", storePath); + + try (IndentXMLStreamWriter writer = XmlStreams.createWriter(storePath)) { + writer.writeStartDocument(ENCODING, VERSION); + + writeRepositoriesStart(writer, creationTime, lastModified); + for (Map.Entry e : pathDatabase.entrySet()) { + writeRepository(writer, e.getKey(), e.getValue()); + } + writer.writeEndElement(); + + writer.writeEndDocument(); + } catch (XMLStreamException | IOException ex) { + throw new InternalRepositoryException( + ContextEntry.ContextBuilder.entity(Path.class, storePath.toString()).build(), + "failed to write repository path database", + ex + ); + } + } + + private void ensureParentDirectoryExists() { + Path parent = storePath.getParent(); + if (!Files.exists(parent)) { + try { + Files.createDirectories(parent); + } catch (IOException ex) { + throw new InternalRepositoryException( + ContextEntry.ContextBuilder.entity(Path.class, parent.toString()).build(), + "failed to create parent directory", + ex + ); + } + } + } + + private void writeRepositoriesStart(XMLStreamWriter writer, Long creationTime, Long lastModified) throws XMLStreamException { + writer.writeStartElement(ELEMENT_REPOSITORIES); + writer.writeAttribute(ATTRIBUTE_CREATION_TIME, String.valueOf(creationTime)); + writer.writeAttribute(ATTRIBUTE_LAST_MODIFIED, String.valueOf(lastModified)); + } + + private void writeRepository(XMLStreamWriter writer, String id, Path value) throws XMLStreamException { + writer.writeStartElement(ELEMENT_REPOSITORY); + writer.writeAttribute(ATTRIBUTE_ID, id); + writer.writeCharacters(value.toString()); + writer.writeEndElement(); + } + + void read(OnRepositories onRepositories, OnRepository onRepository) { + LOG.trace("read repository path database from {}", storePath); + XMLStreamReader reader = null; + try { + reader = XmlStreams.createReader(storePath); + + while (reader.hasNext()) { + int eventType = reader.next(); + + if (eventType == XMLStreamReader.START_ELEMENT) { + String element = reader.getLocalName(); + if (ELEMENT_REPOSITORIES.equals(element)) { + readRepositories(reader, onRepositories); + } else if (ELEMENT_REPOSITORY.equals(element)) { + readRepository(reader, onRepository); + } + } + } + } catch (XMLStreamException | IOException ex) { + throw new InternalRepositoryException( + ContextEntry.ContextBuilder.entity(Path.class, storePath.toString()).build(), + "failed to read repository path database", + ex + ); + } finally { + XmlStreams.close(reader); + } + } + + private void readRepository(XMLStreamReader reader, OnRepository onRepository) throws XMLStreamException { + String id = reader.getAttributeValue(null, ATTRIBUTE_ID); + Path path = Paths.get(reader.getElementText()); + onRepository.handle(id, path); + } + + private void readRepositories(XMLStreamReader reader, OnRepositories onRepositories) { + String creationTime = reader.getAttributeValue(null, ATTRIBUTE_CREATION_TIME); + String lastModified = reader.getAttributeValue(null, ATTRIBUTE_LAST_MODIFIED); + onRepositories.handle(Long.parseLong(creationTime), Long.parseLong(lastModified)); + } + + @FunctionalInterface + interface OnRepositories { + + void handle(Long creationTime, Long lastModified); + + } + + @FunctionalInterface + interface OnRepository { + + void handle(String id, Path path); + + } + +} diff --git a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryDAO.java b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryDAO.java index c83de49525..de51ebdef7 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryDAO.java +++ b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryDAO.java @@ -33,156 +33,229 @@ package sonia.scm.repository.xml; //~--- non-JDK imports -------------------------------------------------------- -import com.google.inject.Inject; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; import com.google.inject.Singleton; -import sonia.scm.ContextEntry; import sonia.scm.SCMContextProvider; import sonia.scm.io.FileSystem; import sonia.scm.repository.InitialRepositoryLocationResolver; -import sonia.scm.repository.InitialRepositoryLocationResolver.InitialRepositoryLocation; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.PathBasedRepositoryDAO; import sonia.scm.repository.Repository; -import sonia.scm.store.ConfigurationStoreFactory; -import sonia.scm.xml.AbstractXmlDAO; +import sonia.scm.store.StoreConstants; +import javax.inject.Inject; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; +import java.time.Clock; import java.util.Collection; -import java.util.Optional; +import java.util.LinkedHashMap; +import java.util.Map; /** * @author Sebastian Sdorra */ @Singleton -public class XmlRepositoryDAO - extends AbstractXmlDAO - implements PathBasedRepositoryDAO { +public class XmlRepositoryDAO implements PathBasedRepositoryDAO { - public static final String STORE_NAME = "repositories"; + private static final String STORE_NAME = "repositories"; + + private final PathDatabase pathDatabase; + private final MetadataStore metadataStore = new MetadataStore(); - private InitialRepositoryLocationResolver initialRepositoryLocationResolver; - private final FileSystem fileSystem; private final SCMContextProvider context; + private final InitialRepositoryLocationResolver locationResolver; + private final FileSystem fileSystem; - //~--- constructors --------------------------------------------------------- + @VisibleForTesting + Clock clock = Clock.systemUTC(); + + private Long creationTime; + private Long lastModified; + + private Map pathById; + private Map byId; + private Map byNamespaceAndName; - /** - * Constructs ... - * @param storeFactory - * @param fileSystem - * @param context - */ @Inject - public XmlRepositoryDAO(ConfigurationStoreFactory storeFactory, InitialRepositoryLocationResolver initialRepositoryLocationResolver, FileSystem fileSystem, SCMContextProvider context) { - super(storeFactory.getStore(XmlRepositoryDatabase.class, STORE_NAME)); - this.initialRepositoryLocationResolver = initialRepositoryLocationResolver; - this.fileSystem = fileSystem; + public XmlRepositoryDAO(SCMContextProvider context, InitialRepositoryLocationResolver locationResolver, FileSystem fileSystem) { this.context = context; + this.locationResolver = locationResolver; + this.fileSystem = fileSystem; + + this.creationTime = clock.millis(); + + this.pathById = new LinkedHashMap<>(); + this.byId = new LinkedHashMap<>(); + this.byNamespaceAndName = new LinkedHashMap<>(); + + pathDatabase = new PathDatabase(createStorePath()); + read(); } - //~--- methods -------------------------------------------------------------- + private void read() { + Path storePath = createStorePath(); - @Override - public boolean contains(NamespaceAndName namespaceAndName) { - return db.contains(namespaceAndName); + if (!Files.exists(storePath)) { + return; + } + + pathDatabase.read(this::loadDates, this::loadRepository); } - //~--- get methods ---------------------------------------------------------- - - @Override - public Repository get(NamespaceAndName namespaceAndName) { - return db.get(namespaceAndName); + private void loadDates(Long creationTime, Long lastModified) { + this.creationTime = creationTime; + this.lastModified = lastModified; } - //~--- methods -------------------------------------------------------------- + private void loadRepository(String id, Path repositoryPath) { + Path metadataPath = createMetadataPath(context.resolve(repositoryPath)); + Repository repository = metadataStore.read(metadataPath); + + byId.put(id, repository); + byNamespaceAndName.put(repository.getNamespaceAndName(), repository); + pathById.put(id, repositoryPath); + } + + @VisibleForTesting + Path createStorePath() { + return context.getBaseDirectory() + .toPath() + .resolve(StoreConstants.CONFIG_DIRECTORY_NAME) + .resolve(STORE_NAME.concat(StoreConstants.FILE_EXTENSION)); + } + + + @VisibleForTesting + Path createMetadataPath(Path repositoryPath) { + return repositoryPath.resolve(StoreConstants.REPOSITORY_METADATA.concat(StoreConstants.FILE_EXTENSION)); + } @Override - public void modify(Repository repository) { - RepositoryPath repositoryPath = findExistingRepositoryPath(repository.getId()).orElseThrow(() -> new InternalRepositoryException(repository, "path object for repository not found")); - repositoryPath.setRepository(repository); - repositoryPath.setToBeSynchronized(true); - storeDB(); + public String getType() { + return "xml"; + } + + @Override + public Long getCreationTime() { + return creationTime; + } + + @Override + public Long getLastModified() { + return lastModified; } @Override public void add(Repository repository) { - InitialRepositoryLocation initialLocation = initialRepositoryLocationResolver.getRelativeRepositoryPath(repository.getId()); + Repository clone = repository.clone(); + + Path repositoryPath = locationResolver.getPath(repository.getId()); + Path resolvedPath = context.resolve(repositoryPath); + try { - fileSystem.create(initialLocation.getAbsolutePath()); + fileSystem.create(resolvedPath.toFile()); + + Path metadataPath = createMetadataPath(resolvedPath); + metadataStore.write(metadataPath, repository); + + synchronized (this) { + pathById.put(repository.getId(), repositoryPath); + + byId.put(repository.getId(), clone); + byNamespaceAndName.put(repository.getNamespaceAndName(), clone); + + writePathDatabase(); + } + } catch (IOException e) { - throw new InternalRepositoryException(repository, "could not create directory for repository data: " + initialLocation.getAbsolutePath(), e); - } - RepositoryPath repositoryPath = new RepositoryPath(initialLocation.getRelativePath(), repository.getId(), repository.clone()); - repositoryPath.setToBeSynchronized(true); - synchronized (store) { - db.add(repositoryPath); - storeDB(); + throw new InternalRepositoryException(repository, "failed to create filesystem", e); } } + private void writePathDatabase() { + lastModified = clock.millis(); + pathDatabase.write(creationTime, lastModified, pathById); + } + + @Override + public boolean contains(Repository repository) { + return byId.containsKey(repository.getId()); + } + + @Override + public boolean contains(NamespaceAndName namespaceAndName) { + return byNamespaceAndName.containsKey(namespaceAndName); + } + + @Override + public boolean contains(String id) { + return byId.containsKey(id); + } + + @Override + public Repository get(NamespaceAndName namespaceAndName) { + return byNamespaceAndName.get(namespaceAndName); + } + @Override public Repository get(String id) { - RepositoryPath repositoryPath = db.get(id); - if (repositoryPath != null) { - return repositoryPath.getRepository(); - } - return null; + return byId.get(id); } @Override public Collection getAll() { - return db.getRepositories(); + return ImmutableList.copyOf(byNamespaceAndName.values()); } - /** - * Method description - * - * @param repository - * @return - */ @Override - protected Repository clone(Repository repository) { - return repository.clone(); + public void modify(Repository repository) { + Repository clone = repository.clone(); + + synchronized (this) { + // remove old namespaceAndName from map, in case of rename + Repository prev = byId.put(clone.getId(), clone); + if (prev != null) { + byNamespaceAndName.remove(prev.getNamespaceAndName()); + } + byNamespaceAndName.put(clone.getNamespaceAndName(), clone); + + writePathDatabase(); + } + + Path repositoryPath = context.resolve(getPath(repository.getId())); + Path metadataPath = createMetadataPath(repositoryPath); + metadataStore.write(metadataPath, clone); } @Override public void delete(Repository repository) { - Path directory = getPath(repository.getId()); - super.delete(repository); - try { - fileSystem.destroy(directory.toFile()); - } catch (IOException e) { - throw new InternalRepositoryException(repository, "could not delete repository directory", e); - } - } + Path path; + synchronized (this) { + Repository prev = byId.remove(repository.getId()); + if (prev != null) { + byNamespaceAndName.remove(prev.getNamespaceAndName()); + } - /** - * Method description - * - * @return - */ - @Override - protected XmlRepositoryDatabase createNewDatabase() { - return new XmlRepositoryDatabase(); + path = pathById.remove(repository.getId()); + + writePathDatabase(); + } + + path = context.resolve(path); + + try { + fileSystem.destroy(path.toFile()); + } catch (IOException e) { + throw new InternalRepositoryException(repository, "failed to destroy filesystem", e); + } } @Override public Path getPath(String repositoryId) { - return context - .getBaseDirectory() - .toPath() - .resolve( - findExistingRepositoryPath(repositoryId) - .map(RepositoryPath::getPath) - .orElseThrow(() -> new InternalRepositoryException(ContextEntry.ContextBuilder.entity("repository", repositoryId), "could not find base directory for repository"))); - } - - private Optional findExistingRepositoryPath(String repositoryId) { - return db.values().stream() - .filter(repoPath -> repoPath.getId().equals(repositoryId)) - .findAny(); + return pathById.get(repositoryId); } } diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStore.java b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStore.java index 6a9098b545..40cf03c8a8 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStore.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStore.java @@ -35,32 +35,14 @@ package sonia.scm.store; //~--- non-JDK imports -------------------------------------------------------- -import com.google.common.base.Charsets; import com.google.common.base.Predicate; import com.google.common.collect.Collections2; import com.google.common.collect.Maps; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import sonia.scm.security.KeyGenerator; import sonia.scm.xml.IndentXMLStreamWriter; - -//~--- JDK imports ------------------------------------------------------------ - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.Reader; -import java.io.Writer; - -import java.util.Collection; -import java.util.Collections; -import java.util.Map; -import java.util.Map.Entry; +import sonia.scm.xml.XmlStreams; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; @@ -68,11 +50,14 @@ import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import javax.xml.namespace.QName; -import javax.xml.stream.XMLInputFactory; -import javax.xml.stream.XMLOutputFactory; -import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; -import javax.xml.stream.XMLStreamWriter; +import java.io.File; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; + +//~--- JDK imports ------------------------------------------------------------ /** * @@ -255,74 +240,6 @@ public class JAXBConfigurationEntryStore implements ConfigurationEntryStore implements ConfigurationEntryStore implements ConfigurationEntryStore implements ConfigurationEntryStore implements ConfigurationEntryStore store; - @Mock - private XmlRepositoryDatabase db; @Mock private SCMContextProvider context; - @Rule - public TemporaryFolder temporaryFolder = new TemporaryFolder(); + @Mock + private InitialRepositoryLocationResolver locationResolver; - private final FileSystem fileSystem = new DefaultFileSystem(); + private FileSystem fileSystem = new DefaultFileSystem(); - @Before - public void init() throws IOException { - when(storeFactory.getStore(XmlRepositoryDatabase.class, STORE_NAME)).thenReturn(store); - when(store.get()).thenReturn(db); - when(context.getBaseDirectory()).thenReturn(temporaryFolder.newFolder()); + private XmlRepositoryDAO dao; + + private Path baseDirectory; + + private AtomicLong atomicClock; + + @BeforeEach + void createDAO(@TempDirectory.TempDir Path baseDirectory) { + this.baseDirectory = baseDirectory; + this.atomicClock = new AtomicLong(); + + when(locationResolver.getPath("42")).thenReturn(Paths.get("repos", "42")); + when(locationResolver.getPath("42+1")).thenReturn(Paths.get("repos", "puzzle")); + + when(context.getBaseDirectory()).thenReturn(baseDirectory.toFile()); + when(context.resolve(any(Path.class))).then(ic -> { + Path path = ic.getArgument(0); + return baseDirectory.resolve(path); + }); + + dao = createDAO(); + } + + private XmlRepositoryDAO createDAO() { + XmlRepositoryDAO dao = new XmlRepositoryDAO(context, locationResolver, fileSystem); + + Clock clock = mock(Clock.class); + when(clock.millis()).then(ic -> atomicClock.incrementAndGet()); + dao.clock = clock; + + return dao; } @Test - public void addShouldCreateNewRepositoryPathWithRelativePath() { - InitialRepositoryLocationResolver initialRepositoryLocationResolver = new InitialRepositoryLocationResolver(context); - XmlRepositoryDAO dao = new XmlRepositoryDAO(storeFactory, initialRepositoryLocationResolver, fileSystem, context); - - dao.add(new Repository("id", null, null, null)); - - verify(db).add(argThat(repositoryPath -> { - assertThat(repositoryPath.getId()).isEqualTo("id"); - assertThat(repositoryPath.getPath()).isEqualTo(InitialRepositoryLocationResolver.DEFAULT_REPOSITORY_PATH + "/id"); - return true; - })); - verify(store).set(db); + void shouldReturnXmlType() { + assertThat(dao.getType()).isEqualTo("xml"); } @Test - public void modifyShouldStoreChangedRepository() { - Repository oldRepository = new Repository("id", "old", null, null); - RepositoryPath repositoryPath = new RepositoryPath("/path", "id", oldRepository); - when(db.values()).thenReturn(asList(repositoryPath)); - - XmlRepositoryDAO dao = new XmlRepositoryDAO(storeFactory, new InitialRepositoryLocationResolver(context), fileSystem, context); - - Repository newRepository = new Repository("id", "new", null, null); - dao.modify(newRepository); - - assertThat(repositoryPath.getRepository()).isSameAs(newRepository); - verify(store).set(db); + void shouldReturnCreationTimeAfterCreation() { + long now = System.currentTimeMillis(); + assertThat(dao.getCreationTime()).isBetween(now - 200, now + 200); } @Test - public void shouldGetPathInBaseDirectoryForRelativePath() { - Repository existingRepository = new Repository("id", "old", null, null); - RepositoryPath repositoryPath = new RepositoryPath("path", "id", existingRepository); - when(db.values()).thenReturn(asList(repositoryPath)); - - XmlRepositoryDAO dao = new XmlRepositoryDAO(storeFactory, new InitialRepositoryLocationResolver(context), fileSystem, context); - - Path path = dao.getPath(existingRepository.getId()); - - assertThat(path.toString()).isEqualTo(context.getBaseDirectory().getPath() + "/path"); + void shouldNotReturnLastModifiedAfterCreation() { + assertThat(dao.getLastModified()).isNull(); } @Test - public void shouldGetPathInBaseDirectoryForAbsolutePath() { - Repository existingRepository = new Repository("id", "old", null, null); - RepositoryPath repositoryPath = new RepositoryPath("/tmp/path", "id", existingRepository); - when(db.values()).thenReturn(asList(repositoryPath)); + void shouldReturnTrueForEachContainsMethod() { + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); - XmlRepositoryDAO dao = new XmlRepositoryDAO(storeFactory, new InitialRepositoryLocationResolver(context), fileSystem, context); + assertThat(dao.contains(heartOfGold)).isTrue(); + assertThat(dao.contains(heartOfGold.getId())).isTrue(); + assertThat(dao.contains(heartOfGold.getNamespaceAndName())).isTrue(); + } - Path path = dao.getPath(existingRepository.getId()); + private Repository createHeartOfGold() { + Repository heartOfGold = RepositoryTestData.createHeartOfGold(); + heartOfGold.setId("42"); + return heartOfGold; + } - assertThat(path.toString()).isEqualTo("/tmp/path"); + @Test + void shouldReturnFalseForEachContainsMethod() { + Repository heartOfGold = createHeartOfGold(); + + assertThat(dao.contains(heartOfGold)).isFalse(); + assertThat(dao.contains(heartOfGold.getId())).isFalse(); + assertThat(dao.contains(heartOfGold.getNamespaceAndName())).isFalse(); + } + + @Test + void shouldReturnNullForEachGetMethod() { + assertThat(dao.get("42")).isNull(); + assertThat(dao.get(new NamespaceAndName("hitchhiker","HeartOfGold"))).isNull(); + } + + @Test + void shouldReturnRepository() { + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); + + assertThat(dao.get("42")).isEqualTo(heartOfGold); + assertThat(dao.get(new NamespaceAndName("hitchhiker","HeartOfGold"))).isEqualTo(heartOfGold); + } + + @Test + void shouldNotReturnTheSameInstance() { + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); + + Repository repository = dao.get("42"); + assertThat(repository).isNotSameAs(heartOfGold); + } + + @Test + void shouldReturnAllRepositories() { + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); + + Repository puzzle = createPuzzle(); + dao.add(puzzle); + + Collection repositories = dao.getAll(); + assertThat(repositories).containsExactlyInAnyOrder(heartOfGold, puzzle); + } + + private Repository createPuzzle() { + Repository puzzle = RepositoryTestData.create42Puzzle(); + puzzle.setId("42+1"); + return puzzle; + } + + @Test + void shouldModifyRepository() { + Repository heartOfGold = createHeartOfGold(); + heartOfGold.setDescription("HeartOfGold"); + dao.add(heartOfGold); + assertThat(dao.get("42").getDescription()).isEqualTo("HeartOfGold"); + + heartOfGold = createHeartOfGold(); + heartOfGold.setDescription("Heart of Gold"); + dao.modify(heartOfGold); + + assertThat(dao.get("42").getDescription()).isEqualTo("Heart of Gold"); + } + + @Test + void shouldRemoveRepository() { + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); + assertThat(dao.contains("42")).isTrue(); + + dao.delete(heartOfGold); + assertThat(dao.contains("42")).isFalse(); + assertThat(dao.contains(new NamespaceAndName("hitchhiker", "HeartOfGold"))).isFalse(); + } + + @Test + void shouldUpdateLastModifiedAfterEachWriteOperation() { + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); + + Long firstLastModified = dao.getLastModified(); + assertThat(firstLastModified).isNotNull(); + + Repository puzzle = createPuzzle(); + dao.add(puzzle); + + Long lastModifiedAdded = dao.getLastModified(); + assertThat(lastModifiedAdded).isGreaterThan(firstLastModified); + + heartOfGold.setDescription("Heart of Gold"); + dao.modify(heartOfGold); + + Long lastModifiedModified = dao.getLastModified(); + assertThat(lastModifiedModified).isGreaterThan(lastModifiedAdded); + + dao.delete(puzzle); + + Long lastModifiedRemoved = dao.getLastModified(); + assertThat(lastModifiedRemoved).isGreaterThan(lastModifiedModified); + } + + @Test + void shouldRenameTheRepository() { + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); + + heartOfGold.setNamespace("hg2tg"); + heartOfGold.setName("hog"); + + dao.modify(heartOfGold); + + Repository repository = dao.get("42"); + assertThat(repository.getNamespace()).isEqualTo("hg2tg"); + assertThat(repository.getName()).isEqualTo("hog"); + + assertThat(dao.contains(new NamespaceAndName("hg2tg", "hog"))).isTrue(); + assertThat(dao.contains(new NamespaceAndName("hitchhiker", "HeartOfGold"))).isFalse(); + } + + @Test + void shouldDeleteRepositoryEvenWithChangedNamespace() { + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); + + heartOfGold.setNamespace("hg2tg"); + heartOfGold.setName("hog"); + + dao.delete(heartOfGold); + + assertThat(dao.contains(new NamespaceAndName("hitchhiker", "HeartOfGold"))).isFalse(); + } + + @Test + void shouldReturnThePathForTheRepository() { + Path repositoryPath = Paths.get("r", "42"); + when(locationResolver.getPath("42")).thenReturn(repositoryPath); + + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); + + Path path = dao.getPath("42"); + assertThat(path).isEqualTo(repositoryPath); + } + + @Test + void shouldCreateTheDirectoryForTheRepository() { + Path repositoryPath = Paths.get("r", "42"); + when(locationResolver.getPath("42")).thenReturn(repositoryPath); + + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); + + Path path = getAbsolutePathFromDao("42"); + assertThat(path).isDirectory(); + } + + @Test + void shouldRemoveRepositoryDirectoryAfterDeletion() { + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); + + Path path = getAbsolutePathFromDao(heartOfGold.getId()); + assertThat(path).isDirectory(); + + dao.delete(heartOfGold); + assertThat(path).doesNotExist(); + } + + private Path getAbsolutePathFromDao(String id) { + return context.resolve(dao.getPath(id)); + } + + @Test + void shouldCreateRepositoryPathDatabase() throws IOException { + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); + + Path storePath = dao.createStorePath(); + assertThat(storePath).isRegularFile(); + + String content = content(storePath); + + assertThat(content).contains(heartOfGold.getId()); + assertThat(content).contains(dao.getPath(heartOfGold.getId()).toString()); + } + + private String content(Path storePath) throws IOException { + return new String(Files.readAllBytes(storePath), Charsets.UTF_8); + } + + @Test + void shouldStoreRepositoryMetadataAfterAdd() throws IOException { + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); + + Path repositoryDirectory = getAbsolutePathFromDao(heartOfGold.getId()); + Path metadataPath = dao.createMetadataPath(repositoryDirectory); + + assertThat(metadataPath).isRegularFile(); + + String content = content(metadataPath); + assertThat(content).contains(heartOfGold.getName()); + assertThat(content).contains(heartOfGold.getNamespace()); + assertThat(content).contains(heartOfGold.getDescription()); + } + + @Test + void shouldUpdateRepositoryMetadataAfterModify() throws IOException { + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); + + heartOfGold.setDescription("Awesome Spaceship"); + dao.modify(heartOfGold); + + Path repositoryDirectory = getAbsolutePathFromDao(heartOfGold.getId()); + Path metadataPath = dao.createMetadataPath(repositoryDirectory); + + String content = content(metadataPath); + assertThat(content).contains("Awesome Spaceship"); + } + + @Test + void shouldReadPathDatabaseAndMetadataOfRepositories() { + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); + + // reload data + dao = createDAO(); + + heartOfGold = dao.get("42"); + assertThat(heartOfGold.getName()).isEqualTo("HeartOfGold"); + + Path path = getAbsolutePathFromDao(heartOfGold.getId()); + assertThat(path).isDirectory(); + } + + @Test + void shouldReadCreationTimeAndLastModifedDateFromDatabase() { + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); + + Long creationTime = dao.getCreationTime(); + Long lastModified = dao.getLastModified(); + + // reload data + dao = createDAO(); + + assertThat(dao.getCreationTime()).isEqualTo(creationTime); + assertThat(dao.getLastModified()).isEqualTo(lastModified); } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryHandlerTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryHandlerTest.java index bfff7d3bf1..d3eca1d6e0 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryHandlerTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryHandlerTest.java @@ -62,8 +62,6 @@ public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { @Mock private GitWorkdirFactory gitWorkdirFactory; - RepositoryLocationResolver repositoryLocationResolver; - @Override protected void checkDirectory(File directory) { @@ -86,10 +84,10 @@ public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { @Override protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, + RepositoryLocationResolver locationResolver, File directory) { - repositoryLocationResolver = new RepositoryLocationResolver(repoDao, new InitialRepositoryLocationResolver(contextProvider)); GitRepositoryHandler repositoryHandler = new GitRepositoryHandler(factory, - scheduler, repositoryLocationResolver, gitWorkdirFactory); + scheduler, locationResolver, gitWorkdirFactory); repositoryHandler.init(contextProvider); GitConfig config = new GitConfig(); @@ -103,7 +101,7 @@ public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { @Test public void getDirectory() { GitRepositoryHandler repositoryHandler = new GitRepositoryHandler(factory, - scheduler, repositoryLocationResolver, gitWorkdirFactory); + scheduler, locationResolver, gitWorkdirFactory); GitConfig config = new GitConfig(); config.setDisabled(false); config.setGcExpression("gc exp"); diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java index b8b9646a90..7a13c06eb2 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java @@ -50,7 +50,7 @@ import static org.junit.Assert.assertTrue; /** * @author Sebastian Sdorra */ -@RunWith(MockitoJUnitRunner.class) +@RunWith(MockitoJUnitRunner.Silent.class) public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { @Mock @@ -59,8 +59,6 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { @Mock private com.google.inject.Provider provider; - private RepositoryLocationResolver repositoryLocationResolver; - @Override protected void checkDirectory(File directory) { File hgDirectory = new File(directory, ".hg"); @@ -70,11 +68,8 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { } @Override - protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, - File directory) { - repositoryLocationResolver = new RepositoryLocationResolver(repoDao, new InitialRepositoryLocationResolver(contextProvider)); - HgRepositoryHandler handler = new HgRepositoryHandler(factory, - new HgContextProvider(), repositoryLocationResolver); + protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, RepositoryLocationResolver locationResolver, File directory) { + HgRepositoryHandler handler = new HgRepositoryHandler(factory, new HgContextProvider(), locationResolver); handler.init(contextProvider); HgTestUtil.checkForSkip(handler); @@ -84,8 +79,7 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { @Test public void getDirectory() { - HgRepositoryHandler repositoryHandler = new HgRepositoryHandler(factory, - provider, repositoryLocationResolver); + HgRepositoryHandler repositoryHandler = new HgRepositoryHandler(factory, provider, locationResolver); HgConfig hgConfig = new HgConfig(); hgConfig.setHgBinary("hg"); diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java index d2344816ef..68f7e18a76 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java @@ -103,7 +103,7 @@ public final class HgTestUtil PathBasedRepositoryDAO repoDao = mock(PathBasedRepositoryDAO.class); - RepositoryLocationResolver repositoryLocationResolver = new RepositoryLocationResolver(repoDao, new InitialRepositoryLocationResolver(context)); + RepositoryLocationResolver repositoryLocationResolver = new RepositoryLocationResolver(context, repoDao, new InitialRepositoryLocationResolver()); HgRepositoryHandler handler = new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), new HgContextProvider(), repositoryLocationResolver); Path repoDir = directory.toPath(); diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/TempSCMContextProvider.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/TempSCMContextProvider.java index bc6794e5a5..0a0064ad44 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/TempSCMContextProvider.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/TempSCMContextProvider.java @@ -41,6 +41,7 @@ import sonia.scm.Stage; import java.io.File; import java.io.IOException; +import java.nio.file.Path; /** * @@ -136,6 +137,11 @@ public class TempSCMContextProvider implements SCMContextProvider this.baseDirectory = baseDirectory; } + @Override + public Path resolve(Path path) { + return baseDirectory.toPath().resolve(path); + } + //~--- fields --------------------------------------------------------------- /** Field description */ diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java index bfc8bbc428..7b11d1bb7f 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java @@ -67,15 +67,10 @@ public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { @Mock private com.google.inject.Provider repositoryManagerProvider; - @Mock - private RepositoryDAO repositoryDAO; - private HookContextFactory hookContextFactory = new HookContextFactory(mock(PreProcessorUtil.class)); private HookEventFacade facade = new HookEventFacade(repositoryManagerProvider, hookContextFactory); - private RepositoryLocationResolver repositoryLocationResolver; - @Override protected void checkDirectory(File directory) { File format = new File(directory, "format"); @@ -91,9 +86,9 @@ public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { @Override protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, + RepositoryLocationResolver locationResolver, File directory) { - repositoryLocationResolver = new RepositoryLocationResolver(repoDao, new InitialRepositoryLocationResolver(contextProvider)); - SvnRepositoryHandler handler = new SvnRepositoryHandler(factory, null, repositoryLocationResolver); + SvnRepositoryHandler handler = new SvnRepositoryHandler(factory, null, locationResolver); handler.init(contextProvider); @@ -109,7 +104,7 @@ public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { public void getDirectory() { when(factory.getStore(any(), any())).thenReturn(store); SvnRepositoryHandler repositoryHandler = new SvnRepositoryHandler(factory, - facade, repositoryLocationResolver); + facade, locationResolver); SvnConfig svnConfig = new SvnConfig(); repositoryHandler.setConfig(svnConfig); diff --git a/scm-test/src/main/java/sonia/scm/repository/RepositoryTestData.java b/scm-test/src/main/java/sonia/scm/repository/RepositoryTestData.java index b81c39ca00..5dbd672b98 100644 --- a/scm-test/src/main/java/sonia/scm/repository/RepositoryTestData.java +++ b/scm-test/src/main/java/sonia/scm/repository/RepositoryTestData.java @@ -45,6 +45,7 @@ public final class RepositoryTestData { .type(type) .contact("douglas.adams@hitchhiker.com") .name("42Puzzle") + .namespace("hitchhiker") .description("The 42 Puzzle") .build(); } @@ -59,6 +60,7 @@ public final class RepositoryTestData { .type(type) .contact("zaphod.beeblebrox@hitchhiker.com") .name("happyVerticalPeopleTransporter") + .namespace("hitchhiker") .description("Happy Vertical People Transporter") .build(); } @@ -72,6 +74,7 @@ public final class RepositoryTestData { .type(type) .contact("zaphod.beeblebrox@hitchhiker.com") .name("HeartOfGold") + .namespace("hitchhiker") .description( "Heart of Gold is the first prototype ship to successfully utilise the revolutionary Infinite Improbability Drive") .build(); @@ -87,6 +90,7 @@ public final class RepositoryTestData { .type(type) .contact("douglas.adams@hitchhiker.com") .name("RestaurantAtTheEndOfTheUniverse") + .namespace("hitchhiker") .description("The Restaurant at the End of the Universe") .build(); } diff --git a/scm-test/src/main/java/sonia/scm/repository/SimpleRepositoryHandlerTestBase.java b/scm-test/src/main/java/sonia/scm/repository/SimpleRepositoryHandlerTestBase.java index 706117b2c7..37f7266984 100644 --- a/scm-test/src/main/java/sonia/scm/repository/SimpleRepositoryHandlerTestBase.java +++ b/scm-test/src/main/java/sonia/scm/repository/SimpleRepositoryHandlerTestBase.java @@ -45,6 +45,7 @@ import java.nio.file.Path; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -63,7 +64,7 @@ public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase { protected abstract void checkDirectory(File directory); protected abstract RepositoryHandler createRepositoryHandler( - ConfigurationStoreFactory factory, File directory) throws IOException, RepositoryPathNotFoundException; + ConfigurationStoreFactory factory, RepositoryLocationResolver locationResolver, File directory) throws IOException, RepositoryPathNotFoundException; @Test public void testCreate() { @@ -75,7 +76,15 @@ public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase { InMemoryConfigurationStoreFactory storeFactory = new InMemoryConfigurationStoreFactory(); baseDirectory = new File(contextProvider.getBaseDirectory(), "repositories"); IOUtil.mkdirs(baseDirectory); - handler = createRepositoryHandler(storeFactory, baseDirectory); + + locationResolver = mock(RepositoryLocationResolver.class); + + when(locationResolver.getPath(anyString())).then(ic -> { + String id = ic.getArgument(0); + return baseDirectory.toPath().resolve(id); + }); + + handler = createRepositoryHandler(storeFactory, locationResolver, baseDirectory); } @Override @@ -105,6 +114,7 @@ public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase { } protected File baseDirectory; + protected RepositoryLocationResolver locationResolver; private RepositoryHandler handler; } diff --git a/scm-test/src/main/java/sonia/scm/util/MockUtil.java b/scm-test/src/main/java/sonia/scm/util/MockUtil.java index 756b2632be..76bf4ae24d 100644 --- a/scm-test/src/main/java/sonia/scm/util/MockUtil.java +++ b/scm-test/src/main/java/sonia/scm/util/MockUtil.java @@ -55,6 +55,7 @@ import static org.mockito.Mockito.*; import java.io.File; +import java.nio.file.Path; import java.util.Arrays; import java.util.List; @@ -213,6 +214,10 @@ public final class MockUtil SCMContextProvider provider = mock(SCMContextProvider.class); when(provider.getBaseDirectory()).thenReturn(directory); + when(provider.resolve(any(Path.class))).then(ic -> { + Path p = ic.getArgument(0); + return directory.toPath().resolve(p); + }); return provider; } diff --git a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java index bb7c861d33..141a7f8527 100644 --- a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java @@ -434,9 +434,9 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase { DefaultFileSystem fileSystem = new DefaultFileSystem(); Set handlerSet = new HashSet<>(); ConfigurationStoreFactory factory = new JAXBConfigurationStoreFactory(contextProvider); - InitialRepositoryLocationResolver initialRepositoryLocationResolver = new InitialRepositoryLocationResolver(contextProvider); - XmlRepositoryDAO repositoryDAO = new XmlRepositoryDAO(factory, initialRepositoryLocationResolver, fileSystem, contextProvider); - RepositoryLocationResolver repositoryLocationResolver = new RepositoryLocationResolver(repositoryDAO, initialRepositoryLocationResolver); + InitialRepositoryLocationResolver initialRepositoryLocationResolver = new InitialRepositoryLocationResolver(); + XmlRepositoryDAO repositoryDAO = new XmlRepositoryDAO(contextProvider, initialRepositoryLocationResolver, fileSystem); + RepositoryLocationResolver repositoryLocationResolver = new RepositoryLocationResolver(contextProvider, repositoryDAO, initialRepositoryLocationResolver); handlerSet.add(new DummyRepositoryHandler(factory, repositoryLocationResolver)); handlerSet.add(new DummyRepositoryHandler(factory, repositoryLocationResolver) { @Override