Move import logic to dedicated classes

This commit is contained in:
René Pfeuffer
2021-02-22 16:57:54 +01:00
parent bfb6a39631
commit 468b37f725
10 changed files with 522 additions and 242 deletions

View File

@@ -90,5 +90,6 @@ public class MapperModule extends AbstractModule {
bind(ApiKeyToApiKeyDtoMapper.class).to(Mappers.getMapperClass(ApiKeyToApiKeyDtoMapper.class));
bind(RepositoryExportInformationToDtoMapper.class).to(Mappers.getMapperClass(RepositoryExportInformationToDtoMapper.class));
bind(RepositoryImportDtoToRepositoryImportParametersMapper.class).to(Mappers.getMapperClass(RepositoryImportDtoToRepositoryImportParametersMapper.class));
}
}

View File

@@ -80,8 +80,8 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static sonia.scm.ContextEntry.ContextBuilder.entity;
import static sonia.scm.api.v2.resources.RepositoryTypeSupportChecker.checkSupport;
import static sonia.scm.api.v2.resources.RepositoryTypeSupportChecker.type;
import static sonia.scm.importexport.RepositoryTypeSupportChecker.checkSupport;
import static sonia.scm.importexport.RepositoryTypeSupportChecker.type;
public class RepositoryExportResource {

View File

@@ -0,0 +1,33 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.api.v2.resources;
import org.mapstruct.Mapper;
import sonia.scm.importexport.FromUrlImporter;
@Mapper
public interface RepositoryImportDtoToRepositoryImportParametersMapper {
FromUrlImporter.RepositoryImportParameters map(RepositoryImportResource.RepositoryImportFromUrlDto dto);
}

View File

@@ -27,10 +27,8 @@ package sonia.scm.api.v2.resources;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.io.ByteSource;
import com.google.common.io.Files;
import com.google.inject.Inject;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
@@ -39,30 +37,20 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.shiro.SecurityUtils;
import org.jboss.resteasy.plugins.providers.multipart.InputPart;
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
import org.jboss.resteasy.plugins.providers.multipart.MultipartInputImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.ContextEntry;
import sonia.scm.HandlerEventType;
import sonia.scm.Type;
import sonia.scm.event.ScmEventBus;
import sonia.scm.importexport.FromBundleImporter;
import sonia.scm.importexport.FromUrlImporter;
import sonia.scm.importexport.FullScmRepositoryImporter;
import sonia.scm.importexport.RepositoryImportExportEncryption;
import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryImportEvent;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryPermission;
import sonia.scm.repository.RepositoryPermissions;
import sonia.scm.repository.api.Command;
import sonia.scm.repository.api.ImportFailedException;
import sonia.scm.repository.api.PullCommandBuilder;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.util.IOUtil;
import sonia.scm.web.VndMediaType;
import sonia.scm.web.api.DtoValidator;
@@ -80,46 +68,40 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.singletonList;
import static sonia.scm.api.v2.resources.RepositoryTypeSupportChecker.checkSupport;
import static sonia.scm.api.v2.resources.RepositoryTypeSupportChecker.type;
public class RepositoryImportResource {
private static final Logger logger = LoggerFactory.getLogger(RepositoryImportResource.class);
private final RepositoryManager manager;
private final RepositoryDtoToRepositoryMapper mapper;
private final RepositoryServiceFactory serviceFactory;
private final ResourceLinks resourceLinks;
private final ScmEventBus eventBus;
private final FullScmRepositoryImporter fullScmRepositoryImporter;
private final RepositoryImportExportEncryption repositoryImportExportEncryption;
private final RepositoryImportDtoToRepositoryImportParametersMapper importParametersMapper;
private final FromUrlImporter fromUrlImporter;
private final FromBundleImporter fromBundleImporter;
@Inject
public RepositoryImportResource(RepositoryManager manager,
RepositoryDtoToRepositoryMapper mapper,
RepositoryServiceFactory serviceFactory,
public RepositoryImportResource(RepositoryDtoToRepositoryMapper mapper,
ResourceLinks resourceLinks,
ScmEventBus eventBus,
FullScmRepositoryImporter fullScmRepositoryImporter,
RepositoryImportExportEncryption repositoryImportExportEncryption) {
this.manager = manager;
RepositoryImportDtoToRepositoryImportParametersMapper importParametersMapper,
RepositoryImportExportEncryption repositoryImportExportEncryption, FromUrlImporter fromUrlImporter,
FromBundleImporter fromBundleImporter) {
this.mapper = mapper;
this.serviceFactory = serviceFactory;
this.resourceLinks = resourceLinks;
this.eventBus = eventBus;
this.fullScmRepositoryImporter = fullScmRepositoryImporter;
this.repositoryImportExportEncryption = repositoryImportExportEncryption;
this.importParametersMapper = importParametersMapper;
this.fromUrlImporter = fromUrlImporter;
this.fromBundleImporter = fromBundleImporter;
}
/**
@@ -166,49 +148,13 @@ public class RepositoryImportResource {
public Response importFromUrl(@Context UriInfo uriInfo,
@Pattern(regexp = "\\w{1,10}") @PathParam("type") String type,
@Valid RepositoryImportResource.RepositoryImportFromUrlDto request) {
RepositoryPermissions.create().check();
Type t = type(manager, type);
if (!t.getName().equals(request.getType())) {
if (!type.equals(request.getType())) {
throw new WebApplicationException("type of import url and repository does not match", Response.Status.BAD_REQUEST);
}
checkSupport(t, Command.PULL);
logger.info("start {} import for external url {}", type, request.getImportUrl());
Repository repository = fromUrlImporter.importFromUrl(importParametersMapper.map(request), mapper.map(request));
Repository repository = mapper.map(request);
repository.setPermissions(singletonList(new RepositoryPermission(SecurityUtils.getSubject().getPrincipal().toString(), "OWNER", false)));
try {
repository = manager.create(
repository,
pullChangesFromRemoteUrl(request)
);
eventBus.post(new RepositoryImportEvent(HandlerEventType.MODIFY, repository, false));
return Response.created(URI.create(resourceLinks.repository().self(repository.getNamespace(), repository.getName()))).build();
} catch (Exception e) {
eventBus.post(new RepositoryImportEvent(HandlerEventType.MODIFY, repository, true));
throw e;
}
}
@VisibleForTesting
Consumer<Repository> pullChangesFromRemoteUrl(RepositoryImportFromUrlDto request) {
return repository -> {
try (RepositoryService service = serviceFactory.create(repository)) {
PullCommandBuilder pullCommand = service.getPullCommand();
if (!Strings.isNullOrEmpty(request.getUsername()) && !Strings.isNullOrEmpty(request.getPassword())) {
pullCommand
.withUsername(request.getUsername())
.withPassword(request.getPassword());
}
pullCommand.pull(request.getImportUrl());
} catch (IOException e) {
throw new InternalRepositoryException(repository, "Failed to import from remote url", e);
}
};
return Response.created(URI.create(resourceLinks.repository().self(repository.getNamespace(), repository.getName()))).build();
}
/**
@@ -332,25 +278,13 @@ public class RepositoryImportResource {
inputStream = decryptInputStream(inputStream, repositoryDto.getPassword());
}
Type t = type(manager, type);
checkSupport(t, Command.UNBUNDLE);
if (!type.equals(repositoryDto.getType())) {
throw new WebApplicationException("type of import url and repository does not match", Response.Status.BAD_REQUEST);
}
Repository repository = mapper.map(repositoryDto);
repository.setPermissions(singletonList(
new RepositoryPermission(SecurityUtils.getSubject().getPrincipal().toString(), "OWNER", false)
));
try {
repository = manager.create(
repository,
unbundleImport(inputStream, compressed)
);
eventBus.post(new RepositoryImportEvent(HandlerEventType.MODIFY, repository, false));
} catch (Exception e) {
eventBus.post(new RepositoryImportEvent(HandlerEventType.MODIFY, repository, true));
throw e;
}
repository = fromBundleImporter.importFromBundle(compressed, inputStream, repository);
return repository;
}
@@ -363,27 +297,6 @@ public class RepositoryImportResource {
}
}
@VisibleForTesting
Consumer<Repository> unbundleImport(InputStream inputStream, boolean compressed) {
return repository -> {
File file = null;
try (RepositoryService service = serviceFactory.create(repository)) {
file = File.createTempFile("scm-import-", ".bundle");
long length = Files.asByteSink(file).writeFrom(inputStream);
logger.info("copied {} bytes to temp, start bundle import", length);
service.getUnbundleCommand().setCompressed(compressed).unbundle(file);
} catch (IOException e) {
throw new InternalRepositoryException(repository, "Failed to import from bundle", e);
} finally {
try {
IOUtil.delete(file);
} catch (IOException ex) {
logger.warn("could not delete temporary file", ex);
}
}
};
}
private RepositoryImportFromFileDto extractRepositoryDto(Map<String, List<InputPart>> formParts) {
RepositoryImportFromFileDto repositoryDto = extractFromInputPart(formParts.get("repository"), RepositoryImportFromFileDto.class);
checkNotNull(repositoryDto, "repository data is required");

View File

@@ -0,0 +1,109 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.importexport;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.io.Files;
import org.apache.shiro.SecurityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.HandlerEventType;
import sonia.scm.Type;
import sonia.scm.event.ScmEventBus;
import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryImportEvent;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryPermission;
import sonia.scm.repository.api.Command;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.util.IOUtil;
import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.function.Consumer;
import static java.util.Collections.singletonList;
import static sonia.scm.importexport.RepositoryTypeSupportChecker.checkSupport;
import static sonia.scm.importexport.RepositoryTypeSupportChecker.type;
public class FromBundleImporter {
private static final Logger LOG = LoggerFactory.getLogger(FromBundleImporter.class);
private final RepositoryManager manager;
private final RepositoryServiceFactory serviceFactory;
private final ScmEventBus eventBus;
@Inject
public FromBundleImporter(RepositoryManager manager, RepositoryServiceFactory serviceFactory, ScmEventBus eventBus) {
this.manager = manager;
this.serviceFactory = serviceFactory;
this.eventBus = eventBus;
}
public Repository importFromBundle(boolean compressed, InputStream inputStream, Repository repository) {
Type t = type(manager, repository.getType());
checkSupport(t, Command.UNBUNDLE);
repository.setPermissions(singletonList(
new RepositoryPermission(SecurityUtils.getSubject().getPrincipal().toString(), "OWNER", false)
));
try {
repository = manager.create(repository, unbundleImport(inputStream, compressed));
eventBus.post(new RepositoryImportEvent(HandlerEventType.MODIFY, repository, false));
} catch (Exception e) {
eventBus.post(new RepositoryImportEvent(HandlerEventType.MODIFY, repository, true));
throw e;
}
return repository;
}
@VisibleForTesting
Consumer<Repository> unbundleImport(InputStream inputStream, boolean compressed) {
return repository -> {
File file = null;
try (RepositoryService service = serviceFactory.create(repository)) {
file = File.createTempFile("scm-import-", ".bundle");
long length = Files.asByteSink(file).writeFrom(inputStream);
LOG.info("copied {} bytes to temp, start bundle import", length);
service.getUnbundleCommand().setCompressed(compressed).unbundle(file);
} catch (IOException e) {
throw new InternalRepositoryException(repository, "Failed to import from bundle", e);
} finally {
try {
IOUtil.delete(file);
} catch (IOException ex) {
LOG.warn("could not delete temporary file", ex);
}
}
};
}
}

View File

@@ -0,0 +1,119 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.importexport;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import lombok.Getter;
import lombok.Setter;
import org.apache.shiro.SecurityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.HandlerEventType;
import sonia.scm.Type;
import sonia.scm.event.ScmEventBus;
import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryImportEvent;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryPermission;
import sonia.scm.repository.RepositoryPermissions;
import sonia.scm.repository.api.Command;
import sonia.scm.repository.api.PullCommandBuilder;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import javax.inject.Inject;
import java.io.IOException;
import java.util.function.Consumer;
import static java.util.Collections.singletonList;
import static sonia.scm.importexport.RepositoryTypeSupportChecker.checkSupport;
import static sonia.scm.importexport.RepositoryTypeSupportChecker.type;
public class FromUrlImporter {
private static final Logger LOG = LoggerFactory.getLogger(FromUrlImporter.class);
private final RepositoryManager manager;
private final RepositoryServiceFactory serviceFactory;
private final ScmEventBus eventBus;
@Inject
public FromUrlImporter(RepositoryManager manager, RepositoryServiceFactory serviceFactory, ScmEventBus eventBus) {
this.manager = manager;
this.serviceFactory = serviceFactory;
this.eventBus = eventBus;
}
public Repository importFromUrl(RepositoryImportParameters parameters, Repository repository) {
Type t = type(manager, repository.getType());
RepositoryPermissions.create().check();
checkSupport(t, Command.PULL);
LOG.info("start {} import for external url {}", repository.getType(), parameters.getImportUrl());
repository.setPermissions(singletonList(new RepositoryPermission(SecurityUtils.getSubject().getPrincipal().toString(), "OWNER", false)));
try {
repository = manager.create(
repository,
pullChangesFromRemoteUrl(parameters)
);
eventBus.post(new RepositoryImportEvent(HandlerEventType.MODIFY, repository, false));
} catch (Exception e) {
eventBus.post(new RepositoryImportEvent(HandlerEventType.MODIFY, repository, true));
throw e;
}
return repository;
}
@VisibleForTesting
Consumer<Repository> pullChangesFromRemoteUrl(RepositoryImportParameters parameters) {
return repository -> {
try (RepositoryService service = serviceFactory.create(repository)) {
PullCommandBuilder pullCommand = service.getPullCommand();
if (!Strings.isNullOrEmpty(parameters.getUsername()) && !Strings.isNullOrEmpty(parameters.getPassword())) {
pullCommand
.withUsername(parameters.getUsername())
.withPassword(parameters.getPassword());
}
pullCommand.pull(parameters.getImportUrl());
} catch (IOException e) {
throw new InternalRepositoryException(repository, "Failed to import from remote url", e);
}
};
}
@Getter
@Setter
public static class RepositoryImportParameters {
private String importUrl;
private String username;
private String password;
}
}

View File

@@ -22,7 +22,7 @@
* SOFTWARE.
*/
package sonia.scm.api.v2.resources;
package sonia.scm.importexport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -36,7 +36,7 @@ import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import java.util.Set;
class RepositoryTypeSupportChecker {
public class RepositoryTypeSupportChecker {
private RepositoryTypeSupportChecker() {
}
@@ -49,7 +49,7 @@ class RepositoryTypeSupportChecker {
* @param type repository type
* @param cmd command
*/
static void checkSupport(Type type, Command cmd) {
public static void checkSupport(Type type, Command cmd) {
if (!(type instanceof RepositoryType)) {
logger.warn("type {} is not a repository type", type.getName());
throw new WebApplicationException(Response.Status.BAD_REQUEST);
@@ -65,7 +65,7 @@ class RepositoryTypeSupportChecker {
}
@SuppressWarnings("javasecurity:S5145") // the type parameter is validated in the resource to only contain valid characters (\w)
static Type type(RepositoryManager manager, String type) {
public static Type type(RepositoryManager manager, String type) {
RepositoryHandler handler = manager.getHandler(type);
if (handler == null) {
logger.warn("no handler for type {} found", type);

View File

@@ -48,6 +48,8 @@ import sonia.scm.event.ScmEventBus;
import sonia.scm.importexport.ExportFileExtensionResolver;
import sonia.scm.importexport.ExportService;
import sonia.scm.importexport.ExportStatus;
import sonia.scm.importexport.FromBundleImporter;
import sonia.scm.importexport.FromUrlImporter;
import sonia.scm.importexport.FullScmRepositoryExporter;
import sonia.scm.importexport.FullScmRepositoryImporter;
import sonia.scm.importexport.RepositoryImportExportEncryption;
@@ -64,11 +66,8 @@ import sonia.scm.repository.RepositoryType;
import sonia.scm.repository.api.BundleCommandBuilder;
import sonia.scm.repository.api.Command;
import sonia.scm.repository.api.ImportFailedException;
import sonia.scm.repository.api.PullCommandBuilder;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.repository.api.UnbundleCommandBuilder;
import sonia.scm.repository.api.UnbundleResponse;
import sonia.scm.user.User;
import sonia.scm.web.RestDispatcher;
import sonia.scm.web.VndMediaType;
@@ -77,7 +76,6 @@ import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.MediaType;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -92,7 +90,6 @@ import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Predicate;
import static java.util.Collections.singletonList;
@@ -107,13 +104,10 @@ import static javax.servlet.http.HttpServletResponse.SC_OK;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.RETURNS_SELF;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
@@ -166,6 +160,10 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
@Mock
private RepositoryImportExportEncryption repositoryImportExportEncryption;
@Mock
private FromUrlImporter fromUrlImporter;
@Mock
private FromBundleImporter fromBundleImporter;
@Mock
private ExportFileExtensionResolver fileExtensionResolver;
@Mock
private ExportService exportService;
@@ -175,7 +173,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
private final URI baseUri = URI.create("/");
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
private Repository repositoryMarkedAsExported;
@InjectMocks
private RepositoryToRepositoryDtoMapperImpl repositoryToDtoMapper;
@@ -190,7 +187,7 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
super.manager = repositoryManager;
RepositoryCollectionToDtoMapper repositoryCollectionToDtoMapper = new RepositoryCollectionToDtoMapper(repositoryToDtoMapper, resourceLinks);
super.repositoryCollectionResource = new RepositoryCollectionResource(repositoryManager, repositoryCollectionToDtoMapper, dtoToRepositoryMapper, resourceLinks, repositoryInitializer);
super.repositoryImportResource = new RepositoryImportResource(repositoryManager, dtoToRepositoryMapper, serviceFactory, resourceLinks, eventBus, fullScmRepositoryImporter, repositoryImportExportEncryption);
super.repositoryImportResource = new RepositoryImportResource(dtoToRepositoryMapper, resourceLinks, fullScmRepositoryImporter, new RepositoryImportDtoToRepositoryImportParametersMapperImpl(), repositoryImportExportEncryption, fromUrlImporter, fromBundleImporter);
super.repositoryExportResource = new RepositoryExportResource(repositoryManager, serviceFactory, fullScmRepositoryExporter, repositoryImportExportEncryption, exportService, exportInformationToDtoMapper, fileExtensionResolver, resourceLinks);
dispatcher.addSingletonResource(getRepositoryRootResource());
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
@@ -551,88 +548,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
assertThat(captor.getValue().isFailed()).isTrue();
}
@Test
public void shouldPullChangesFromRemoteUrl() throws IOException {
PullCommandBuilder pullCommandBuilder = mock(PullCommandBuilder.class, RETURNS_SELF);
when(service.getPullCommand()).thenReturn(pullCommandBuilder);
Repository repository = RepositoryTestData.createHeartOfGold();
RepositoryImportResource.RepositoryImportFromUrlDto repositoryImportFromUrlDto = new RepositoryImportResource.RepositoryImportFromUrlDto();
repositoryImportFromUrlDto.setImportUrl("https://scm-manager.org/scm/repo/scmadmin/scm-manager.git");
repositoryImportFromUrlDto.setNamespace("scmadmin");
repositoryImportFromUrlDto.setName("scm-manager");
Consumer<Repository> repositoryConsumer = repositoryImportResource.pullChangesFromRemoteUrl(repositoryImportFromUrlDto);
repositoryConsumer.accept(repository);
verify(pullCommandBuilder).pull("https://scm-manager.org/scm/repo/scmadmin/scm-manager.git");
}
@Test
public void shouldPullChangesFromRemoteUrlWithCredentials() {
PullCommandBuilder pullCommandBuilder = mock(PullCommandBuilder.class, RETURNS_SELF);
when(service.getPullCommand()).thenReturn(pullCommandBuilder);
Repository repository = RepositoryTestData.createHeartOfGold();
RepositoryImportResource.RepositoryImportFromUrlDto repositoryImportFromUrlDto = new RepositoryImportResource.RepositoryImportFromUrlDto();
repositoryImportFromUrlDto.setImportUrl("https://scm-manager.org/scm/repo/scmadmin/scm-manager.git");
repositoryImportFromUrlDto.setNamespace("scmadmin");
repositoryImportFromUrlDto.setName("scm-manager");
repositoryImportFromUrlDto.setUsername("trillian");
repositoryImportFromUrlDto.setPassword("secret");
Consumer<Repository> repositoryConsumer = repositoryImportResource.pullChangesFromRemoteUrl(repositoryImportFromUrlDto);
repositoryConsumer.accept(repository);
verify(pullCommandBuilder).withUsername("trillian");
verify(pullCommandBuilder).withPassword("secret");
}
@Test
public void shouldThrowImportFailedEvent() throws IOException {
PullCommandBuilder pullCommandBuilder = mock(PullCommandBuilder.class, RETURNS_SELF);
when(service.getPullCommand()).thenReturn(pullCommandBuilder);
doThrow(ImportFailedException.class).when(pullCommandBuilder).pull(anyString());
Repository repository = RepositoryTestData.createHeartOfGold();
RepositoryImportResource.RepositoryImportFromUrlDto repositoryImportFromUrlDto = new RepositoryImportResource.RepositoryImportFromUrlDto();
repositoryImportFromUrlDto.setImportUrl("https://scm-manager.org/scm/repo/scmadmin/scm-manager.git");
repositoryImportFromUrlDto.setNamespace("scmadmin");
repositoryImportFromUrlDto.setName("scm-manager");
Consumer<Repository> repositoryConsumer = repositoryImportResource.pullChangesFromRemoteUrl(repositoryImportFromUrlDto);
assertThrows(ImportFailedException.class, () -> repositoryConsumer.accept(repository));
}
@Test
public void shouldImportRepositoryFromBundle() throws IOException, URISyntaxException {
when(manager.getHandler("svn")).thenReturn(repositoryHandler);
when(repositoryHandler.getType()).thenReturn(new RepositoryType("svn", "svn", ImmutableSet.of(Command.UNBUNDLE)));
when(repositoryManager.create(any(), any())).thenReturn(RepositoryTestData.createHeartOfGold());
RepositoryDto repositoryDto = new RepositoryDto();
repositoryDto.setName("HeartOfGold");
repositoryDto.setNamespace("hitchhiker");
repositoryDto.setType("svn");
URL dumpUrl = Resources.getResource("sonia/scm/api/v2/svn.dump");
byte[] svnDump = Resources.toByteArray(dumpUrl);
MockHttpRequest request = MockHttpRequest
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "import/svn/bundle");
MockHttpResponse response = new MockHttpResponse();
multipartRequest(request, Collections.singletonMap("bundle", new ByteArrayInputStream(svnDump)), repositoryDto);
dispatcher.invoke(request, response);
assertEquals(HttpServletResponse.SC_CREATED, response.getStatus());
assertEquals("/v2/repositories/hitchhiker/HeartOfGold", response.getOutputHeaders().get("Location").get(0).toString());
ArgumentCaptor<RepositoryImportEvent> event = ArgumentCaptor.forClass(RepositoryImportEvent.class);
verify(eventBus).post(event.capture());
assertFalse(event.getValue().isFailed());
}
@Test
public void shouldThrowFailedEventOnImportRepositoryFromBundle() throws IOException, URISyntaxException {
when(manager.getHandler("svn")).thenReturn(repositoryHandler);
@@ -661,44 +576,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
assertTrue(event.getValue().isFailed());
}
@Test
public void shouldImportCompressedBundle() throws IOException {
URL dumpUrl = Resources.getResource("sonia/scm/api/v2/svn.dump.gz");
byte[] svnDump = Resources.toByteArray(dumpUrl);
UnbundleCommandBuilder ubc = mock(UnbundleCommandBuilder.class, RETURNS_SELF);
when(ubc.unbundle(any(File.class))).thenReturn(new UnbundleResponse(42));
RepositoryService service = mock(RepositoryService.class);
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
when(service.getUnbundleCommand()).thenReturn(ubc);
InputStream in = new ByteArrayInputStream(svnDump);
Consumer<Repository> repositoryConsumer = repositoryImportResource.unbundleImport(in, true);
repositoryConsumer.accept(RepositoryTestData.createHeartOfGold("svn"));
verify(ubc).setCompressed(true);
verify(ubc).unbundle(any(File.class));
}
@Test
public void shouldImportNonCompressedBundle() throws IOException {
URL dumpUrl = Resources.getResource("sonia/scm/api/v2/svn.dump");
byte[] svnDump = Resources.toByteArray(dumpUrl);
UnbundleCommandBuilder ubc = mock(UnbundleCommandBuilder.class, RETURNS_SELF);
when(ubc.unbundle(any(File.class))).thenReturn(new UnbundleResponse(21));
RepositoryService service = mock(RepositoryService.class);
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
when(service.getUnbundleCommand()).thenReturn(ubc);
InputStream in = new ByteArrayInputStream(svnDump);
Consumer<Repository> repositoryConsumer = repositoryImportResource.unbundleImport(in, false);
repositoryConsumer.accept(RepositoryTestData.createHeartOfGold("svn"));
verify(ubc, never()).setCompressed(true);
verify(ubc).unbundle(any(File.class));
}
@Test
public void shouldMarkRepositoryAsArchived() throws Exception {
String namespace = "space";

View File

@@ -0,0 +1,108 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.importexport;
import com.google.common.io.Resources;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.event.ScmEventBus;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryTestData;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.repository.api.UnbundleCommandBuilder;
import sonia.scm.repository.api.UnbundleResponse;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.function.Consumer;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.RETURNS_SELF;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
@SuppressWarnings("UnstableApiUsage")
class FromBundleImporterTest {
@Mock
private RepositoryManager manager;
@Mock
private RepositoryServiceFactory serviceFactory;
@Mock
private ScmEventBus eventBus;
@InjectMocks
private FromBundleImporter importer;
@Test
void shouldImportCompressedBundle() throws IOException {
URL dumpUrl = Resources.getResource("sonia/scm/api/v2/svn.dump.gz");
byte[] svnDump = Resources.toByteArray(dumpUrl);
UnbundleCommandBuilder ubc = mock(UnbundleCommandBuilder.class, RETURNS_SELF);
when(ubc.unbundle(any(File.class))).thenReturn(new UnbundleResponse(42));
RepositoryService service = mock(RepositoryService.class);
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
when(service.getUnbundleCommand()).thenReturn(ubc);
InputStream in = new ByteArrayInputStream(svnDump);
Consumer<Repository> repositoryConsumer = importer.unbundleImport(in, true);
repositoryConsumer.accept(RepositoryTestData.createHeartOfGold("svn"));
verify(ubc).setCompressed(true);
verify(ubc).unbundle(any(File.class));
}
@Test
void shouldImportNonCompressedBundle() throws IOException {
URL dumpUrl = Resources.getResource("sonia/scm/api/v2/svn.dump");
byte[] svnDump = Resources.toByteArray(dumpUrl);
UnbundleCommandBuilder ubc = mock(UnbundleCommandBuilder.class, RETURNS_SELF);
when(ubc.unbundle(any(File.class))).thenReturn(new UnbundleResponse(21));
RepositoryService service = mock(RepositoryService.class);
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
when(service.getUnbundleCommand()).thenReturn(ubc);
InputStream in = new ByteArrayInputStream(svnDump);
Consumer<Repository> repositoryConsumer = importer.unbundleImport(in, false);
repositoryConsumer.accept(RepositoryTestData.createHeartOfGold("svn"));
verify(ubc, never()).setCompressed(true);
verify(ubc).unbundle(any(File.class));
}
}

View File

@@ -0,0 +1,120 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.importexport;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.event.ScmEventBus;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryTestData;
import sonia.scm.repository.api.ImportFailedException;
import sonia.scm.repository.api.PullCommandBuilder;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import java.io.IOException;
import java.util.function.Consumer;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.RETURNS_SELF;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class FromUrlImporterTest {
@Mock
private RepositoryManager manager;
@Mock
private RepositoryServiceFactory serviceFactory;
@Mock
private RepositoryService service;
@Mock
private ScmEventBus eventBus;
@InjectMocks
private FromUrlImporter importer;
@BeforeEach
void setUpMocks() {
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
}
@Test
void shouldPullChangesFromRemoteUrl() throws IOException {
PullCommandBuilder pullCommandBuilder = mock(PullCommandBuilder.class, RETURNS_SELF);
when(service.getPullCommand()).thenReturn(pullCommandBuilder);
Repository repository = RepositoryTestData.createHeartOfGold();
FromUrlImporter.RepositoryImportParameters parameters = new FromUrlImporter.RepositoryImportParameters();
parameters.setImportUrl("https://scm-manager.org/scm/repo/scmadmin/scm-manager.git");
Consumer<Repository> repositoryConsumer = importer.pullChangesFromRemoteUrl(parameters);
repositoryConsumer.accept(repository);
verify(pullCommandBuilder).pull("https://scm-manager.org/scm/repo/scmadmin/scm-manager.git");
}
@Test
void shouldPullChangesFromRemoteUrlWithCredentials() {
PullCommandBuilder pullCommandBuilder = mock(PullCommandBuilder.class, RETURNS_SELF);
when(service.getPullCommand()).thenReturn(pullCommandBuilder);
Repository repository = RepositoryTestData.createHeartOfGold();
FromUrlImporter.RepositoryImportParameters parameters = new FromUrlImporter.RepositoryImportParameters();
parameters.setImportUrl("https://scm-manager.org/scm/repo/scmadmin/scm-manager.git");
parameters.setUsername("trillian");
parameters.setPassword("secret");
Consumer<Repository> repositoryConsumer = importer.pullChangesFromRemoteUrl(parameters);
repositoryConsumer.accept(repository);
verify(pullCommandBuilder).withUsername("trillian");
verify(pullCommandBuilder).withPassword("secret");
}
@Test
void shouldThrowImportFailedEvent() throws IOException {
PullCommandBuilder pullCommandBuilder = mock(PullCommandBuilder.class, RETURNS_SELF);
when(service.getPullCommand()).thenReturn(pullCommandBuilder);
doThrow(ImportFailedException.class).when(pullCommandBuilder).pull(anyString());
Repository repository = RepositoryTestData.createHeartOfGold();
FromUrlImporter.RepositoryImportParameters parameters = new FromUrlImporter.RepositoryImportParameters();
parameters.setImportUrl("https://scm-manager.org/scm/repo/scmadmin/scm-manager.git");
Consumer<Repository> repositoryConsumer = importer.pullChangesFromRemoteUrl(parameters);
assertThrows(ImportFailedException.class, () -> repositoryConsumer.accept(repository));
}
}