Add import protocol (#1558)

Adds a protocol for repository imports (either from an URL, a dump file or a SCM-Manager repository archive).
This protocol documents single steps of an import, the time and the user and is accessible via a dedicated REST
endpoint or a simple ui.

The id of the log is added to the repository imported event, so that plugins like the landingpage or mail can link to these logs.
This commit is contained in:
René Pfeuffer
2021-02-26 13:52:29 +01:00
committed by GitHub
parent ac404b3cdd
commit 0695ca3bac
41 changed files with 1808 additions and 480 deletions

View File

@@ -134,5 +134,6 @@ class IndexDtoGeneratorTest {
when(resourceLinks.namespaceStrategies()).thenReturn(new ResourceLinks.NamespaceStrategiesLinks(scmPathInfo));
when(resourceLinks.namespaceCollection()).thenReturn(new ResourceLinks.NamespaceCollectionLinks(scmPathInfo));
when(resourceLinks.me()).thenReturn(new ResourceLinks.MeLinks(scmPathInfo, new ResourceLinks.UserLinks(scmPathInfo)));
when(resourceLinks.repository()).thenReturn(new ResourceLinks.RepositoryLinks(scmPathInfo));
}
}

View File

@@ -0,0 +1,84 @@
/*
* 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 com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.jboss.resteasy.mock.MockHttpRequest;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.util.Map;
import java.util.UUID;
class MultiPartRequestBuilder {
/**
* This method is a slightly adapted copy of Lin Zaho's gist at https://gist.github.com/lin-zhao/9985191
*/
static void multipartRequest(MockHttpRequest request, Map<String, InputStream> files, RepositoryDto repository) throws IOException {
String boundary = UUID.randomUUID().toString();
request.contentType("multipart/form-data; boundary=" + boundary);
//Make sure this is deleted in afterTest()
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try (OutputStreamWriter formWriter = new OutputStreamWriter(buffer)) {
formWriter.append("--").append(boundary);
for (Map.Entry<String, InputStream> entry : files.entrySet()) {
formWriter.append("\n");
formWriter.append(String.format("Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"",
entry.getKey(), entry.getKey())).append("\n");
formWriter.append("Content-Type: application/octet-stream").append("\n\n");
InputStream stream = entry.getValue();
int b = stream.read();
while (b >= 0) {
formWriter.write(b);
b = stream.read();
}
stream.close();
formWriter.append("\n").append("--").append(boundary);
}
if (repository != null) {
formWriter.append("\n");
formWriter.append("Content-Disposition: form-data; name=\"repository\"").append("\n\n");
StringWriter repositoryWriter = new StringWriter();
new JsonFactory().createGenerator(repositoryWriter).setCodec(new ObjectMapper()).writeObject(repository);
formWriter.append(repositoryWriter.getBuffer().toString()).append("\n");
formWriter.append("--").append(boundary);
}
formWriter.append("--");
formWriter.flush();
}
request.setInputStream(new ByteArrayInputStream(buffer.toByteArray()));
}
}

View File

@@ -0,0 +1,305 @@
/*
* 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 com.google.common.io.Resources;
import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse;
import org.junit.Before;
import org.junit.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.api.v2.resources.RepositoryImportResource.RepositoryImportFromFileDto;
import sonia.scm.importexport.FromBundleImporter;
import sonia.scm.importexport.FromUrlImporter;
import sonia.scm.importexport.FullScmRepositoryImporter;
import sonia.scm.importexport.RepositoryImportExportEncryption;
import sonia.scm.importexport.RepositoryImportLoggerFactory;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryTestData;
import sonia.scm.web.RestDispatcher;
import sonia.scm.web.VndMediaType;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.singletonMap;
import static javax.servlet.http.HttpServletResponse.SC_CREATED;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@SuppressWarnings("UnstableApiUsage")
@RunWith(MockitoJUnitRunner.class)
public class RepositoryImportResourceTest extends RepositoryTestBase {
private final RestDispatcher dispatcher = new RestDispatcher();
@Mock
private FullScmRepositoryImporter fullScmRepositoryImporter;
@Mock
private FromUrlImporter fromUrlImporter;
@Mock
private FromBundleImporter fromBundleImporter;
@Mock
private RepositoryImportLoggerFactory importLoggerFactory;
@Mock
private RepositoryImportExportEncryption repositoryImportExportEncryption;
@Captor
private ArgumentCaptor<FromUrlImporter.RepositoryImportParameters> parametersCaptor;
@Captor
private ArgumentCaptor<Repository> repositoryCaptor;
private final URI baseUri = URI.create("/");
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
@InjectMocks
private RepositoryDtoToRepositoryMapperImpl dtoToRepositoryMapper;
private final MockHttpResponse response = new MockHttpResponse();
@Before
public void prepareEnvironment() {
super.repositoryImportResource = new RepositoryImportResource(dtoToRepositoryMapper, resourceLinks, fullScmRepositoryImporter, new RepositoryImportDtoToRepositoryImportParametersMapperImpl(), repositoryImportExportEncryption, fromUrlImporter, fromBundleImporter, importLoggerFactory);
dispatcher.addSingletonResource(getRepositoryRootResource());
}
@Test
public void shouldImportRepositoryFromUrl() throws Exception {
when(fromUrlImporter.importFromUrl(parametersCaptor.capture(), repositoryCaptor.capture()))
.thenReturn(RepositoryTestData.createHeartOfGold());
URL url = Resources.getResource("sonia/scm/api/v2/import-repo.json");
byte[] importRequest = Resources.toByteArray(url);
MockHttpRequest request = MockHttpRequest
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "import/git/url")
.contentType(VndMediaType.REPOSITORY)
.content(importRequest);
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(SC_CREATED);
assertThat(response.getOutputHeaders().get("Location")).asString().contains("/v2/repositories/hitchhiker/HeartOfGold");
assertThat(parametersCaptor.getValue().getImportUrl()).isEqualTo("https://scm-manager-org/scm/repo/secret/puzzle42");
assertThat(parametersCaptor.getValue().getUsername()).isNull();
assertThat(parametersCaptor.getValue().getPassword()).isNull();
assertThat(repositoryCaptor.getValue().getName()).isEqualTo("HeartOfGold");
assertThat(repositoryCaptor.getValue().getNamespace()).isEqualTo("hitchhiker");
}
@Test
public void shouldImportRepositoryFromUrlWithCredentials() throws Exception {
when(fromUrlImporter.importFromUrl(parametersCaptor.capture(), repositoryCaptor.capture()))
.thenReturn(RepositoryTestData.createHeartOfGold());
URL url = Resources.getResource("sonia/scm/api/v2/import-repo-with-credentials.json");
byte[] importRequest = Resources.toByteArray(url);
MockHttpRequest request = MockHttpRequest
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "import/git/url")
.contentType(VndMediaType.REPOSITORY)
.content(importRequest);
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(SC_CREATED);
assertThat(parametersCaptor.getValue().getUsername()).isEqualTo("trillian");
assertThat(parametersCaptor.getValue().getPassword()).isEqualTo("secret");
}
@Test
public void shouldFailOnImportFromUrlWithDifferentTypes() throws Exception {
URL url = Resources.getResource("sonia/scm/api/v2/import-repo.json");
byte[] importRequest = Resources.toByteArray(url);
MockHttpRequest request = MockHttpRequest
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "import/svn/url")
.contentType(VndMediaType.REPOSITORY)
.content(importRequest);
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isNotEqualTo(SC_CREATED);
verify(fromUrlImporter, never()).importFromUrl(any(), any());
}
@Nested
class WithCorrectBundle {
@BeforeEach
void mockImporter() {
when(
fromBundleImporter.importFromBundle(
eq(false),
argThat(argument -> streamHasContent(argument, "svn-dump")),
argThat(repository -> repository.getName().equals("HeartOfGold"))
)
).thenReturn(RepositoryTestData.createHeartOfGold());
}
@Test
public void shouldImportRepositoryFromBundle() throws Exception {
RepositoryImportFromFileDto importDto = createBasicImportDto();
MockHttpRequest request = MockHttpRequest
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "import/svn/bundle");
MultiPartRequestBuilder.multipartRequest(request, singletonMap("bundle", new ByteArrayInputStream("svn-dump".getBytes(UTF_8))), importDto);
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(SC_CREATED);
assertThat(response.getOutputHeaders().get("Location")).asString().contains("/v2/repositories/hitchhiker/HeartOfGold");
verify(repositoryImportExportEncryption, never()).decrypt(any(), any());
}
@Test
public void shouldImportRepositoryFromEncryptedBundle() throws Exception {
when(repositoryImportExportEncryption.decrypt(any(), eq("hgt2g")))
.thenAnswer(invocation -> invocation.getArgument(0));
RepositoryImportFromFileDto importDto = createBasicImportDto();
importDto.setPassword("hgt2g");
MockHttpRequest request = MockHttpRequest
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "import/svn/bundle");
MultiPartRequestBuilder.multipartRequest(request, singletonMap("bundle", new ByteArrayInputStream("svn-dump".getBytes(UTF_8))), importDto);
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(SC_CREATED);
assertThat(response.getOutputHeaders().get("Location")).asString().contains("/v2/repositories/hitchhiker/HeartOfGold");
}
private RepositoryImportFromFileDto createBasicImportDto() {
RepositoryImportFromFileDto importDto = new RepositoryImportFromFileDto();
importDto.setName("HeartOfGold");
importDto.setNamespace("hitchhiker");
importDto.setType("svn");
return importDto;
}
}
@Test
public void shouldFailOnImportFromBundleWithDifferentTypes() throws Exception {
RepositoryDto repositoryDto = new RepositoryDto();
repositoryDto.setName("HeartOfGold");
repositoryDto.setNamespace("hitchhiker");
repositoryDto.setType("svn");
MockHttpRequest request = MockHttpRequest
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "import/git/bundle");
MultiPartRequestBuilder.multipartRequest(request, Collections.singletonMap("bundle", new ByteArrayInputStream("svn-dump".getBytes(StandardCharsets.UTF_8))), repositoryDto);
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isNotEqualTo(SC_CREATED);
verify(fromBundleImporter, never()).importFromBundle(any(Boolean.class), any(InputStream.class), any(Repository.class));
}
@Test
public void shouldImportFullRepository() throws Exception {
when(
fullScmRepositoryImporter.importFromStream(
argThat(repository -> repository.getName().equals("HeartOfGold")),
argThat(argument -> streamHasContent(argument, "svn-dump")),
isNull()
)
).thenReturn(RepositoryTestData.createHeartOfGold());
RepositoryDto repositoryDto = new RepositoryDto();
repositoryDto.setName("HeartOfGold");
repositoryDto.setNamespace("hitchhiker");
repositoryDto.setType("svn");
MockHttpRequest request = MockHttpRequest
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "import/svn/full");
MultiPartRequestBuilder.multipartRequest(request, singletonMap("bundle", new ByteArrayInputStream("svn-dump".getBytes(UTF_8))), repositoryDto);
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(SC_CREATED);
assertThat(response.getOutputHeaders().get("Location")).asString().contains("/v2/repositories/hitchhiker/HeartOfGold");
}
@Test
public void shouldFindImportLog() throws Exception {
doAnswer(
invocation -> {
invocation.getArgument(1, OutputStream.class).write("some log".getBytes(UTF_8));
return null;
}
).when(importLoggerFactory).getLog(eq("42"), any(OutputStream.class));
MockHttpRequest request = MockHttpRequest
.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "import/log/42");
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(SC_OK);
assertThat(response.getContentAsString()).isEqualTo("some log");
verify(importLoggerFactory).checkCanReadLog("42");
}
private boolean streamHasContent(InputStream argument, String expectedContent) {
try {
byte[] data = new byte[expectedContent.length()];
argument.read(data);
return new String(data).equals(expectedContent);
} catch (IOException e) {
return false;
}
}
}

View File

@@ -24,8 +24,6 @@
package sonia.scm.api.v2.resources;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.sdorra.shiro.ShiroRule;
import com.github.sdorra.shiro.SubjectAware;
import com.google.common.collect.ImmutableSet;
@@ -37,38 +35,36 @@ import org.jboss.resteasy.mock.MockHttpResponse;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.NotFoundException;
import sonia.scm.PageResult;
import sonia.scm.config.ScmConfiguration;
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;
import sonia.scm.importexport.RepositoryImportLoggerFactory;
import sonia.scm.repository.CustomNamespaceStrategy;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.NamespaceStrategy;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryHandler;
import sonia.scm.repository.RepositoryImportEvent;
import sonia.scm.repository.RepositoryInitializer;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryTestData;
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;
@@ -76,23 +72,14 @@ import sonia.scm.web.VndMediaType;
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;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.time.Instant;
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;
@@ -100,27 +87,22 @@ import static java.util.stream.Stream.of;
import static javax.servlet.http.HttpServletResponse.SC_ACCEPTED;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
import static javax.servlet.http.HttpServletResponse.SC_CREATED;
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
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;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.openMocks;
@SubjectAware(
username = "trillian",
@@ -128,6 +110,7 @@ import static org.mockito.MockitoAnnotations.openMocks;
configuration = "classpath:sonia/scm/repository/shiro.ini"
)
@SuppressWarnings("UnstableApiUsage")
@RunWith(MockitoJUnitRunner.class)
public class RepositoryRootResourceTest extends RepositoryTestBase {
private static final String REALM = "AdminRealm";
@@ -156,8 +139,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
@Mock
private Set<NamespaceStrategy> strategies;
@Mock
private ScmEventBus eventBus;
@Mock
private FullScmRepositoryExporter fullScmRepositoryExporter;
@Mock
private RepositoryExportInformationToDtoMapper exportInformationToDtoMapper;
@@ -166,8 +147,14 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
@Mock
private RepositoryImportExportEncryption repositoryImportExportEncryption;
@Mock
private FromUrlImporter fromUrlImporter;
@Mock
private FromBundleImporter fromBundleImporter;
@Mock
private ExportFileExtensionResolver fileExtensionResolver;
@Mock
private RepositoryImportLoggerFactory importLoggerFactory;
@Mock
private ExportService exportService;
@Captor
@@ -175,27 +162,25 @@ 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;
@InjectMocks
private RepositoryDtoToRepositoryMapperImpl dtoToRepositoryMapper;
private final MockHttpResponse response = new MockHttpResponse();
@Before
public void prepareEnvironment() throws IOException {
openMocks(this);
super.repositoryToDtoMapper = repositoryToDtoMapper;
super.dtoToRepositoryMapper = dtoToRepositoryMapper;
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, importLoggerFactory);
super.repositoryExportResource = new RepositoryExportResource(repositoryManager, serviceFactory, fullScmRepositoryExporter, repositoryImportExportEncryption, exportService, exportInformationToDtoMapper, fileExtensionResolver, resourceLinks);
dispatcher.addSingletonResource(getRepositoryRootResource());
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
when(scmPathInfoStore.get()).thenReturn(uriInfo);
when(uriInfo.getApiRestUri()).thenReturn(URI.create("/x/y"));
doReturn(ImmutableSet.of(new CustomNamespaceStrategy()).iterator()).when(strategies).iterator();
SimplePrincipalCollection trillian = new SimplePrincipalCollection("trillian", REALM);
trillian.add(new User("trillian"), REALM);
@@ -213,7 +198,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
createRepository("space", "repo");
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/other");
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
@@ -226,7 +210,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo");
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
@@ -241,7 +224,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
@@ -256,7 +238,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "?q=Rep");
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
@@ -273,7 +254,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space");
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
@@ -290,7 +270,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space?q=Rep");
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
@@ -310,7 +289,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
.put("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo")
.contentType(VndMediaType.REPOSITORY)
.content(repository);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
@@ -328,7 +306,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
.put("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo")
.contentType(VndMediaType.REPOSITORY)
.content(repository);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
@@ -347,7 +324,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
.put("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo")
.contentType(VndMediaType.REPOSITORY)
.content(repository);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
@@ -367,7 +343,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
.put("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "wrong/repo")
.contentType(VndMediaType.REPOSITORY)
.content(repository);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
@@ -380,7 +355,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
createRepository("space", "repo");
MockHttpRequest request = MockHttpRequest.delete("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo");
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
@@ -403,7 +377,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2)
.contentType(VndMediaType.REPOSITORY)
.content(repositoryJson);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
@@ -424,7 +397,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "?initialize=true")
.contentType(VndMediaType.REPOSITORY)
.content(repositoryJson);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
@@ -454,7 +426,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2)
.contentType(VndMediaType.REPOSITORY)
.content(repositoryJson);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
@@ -473,7 +444,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo");
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
@@ -495,7 +465,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/rename")
.contentType(VndMediaType.REPOSITORY)
.content(repository);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
@@ -503,202 +472,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
verify(repositoryManager).rename(repository1, "space", "x");
}
@Test
public void shouldImportRepositoryFromUrl() throws URISyntaxException, IOException {
ArgumentCaptor<RepositoryImportEvent> captor = ArgumentCaptor.forClass(RepositoryImportEvent.class);
when(manager.getHandler("git")).thenReturn(repositoryHandler);
when(repositoryHandler.getType()).thenReturn(new RepositoryType("git", "git", ImmutableSet.of(Command.PULL)));
when(manager.create(any(Repository.class), any())).thenReturn(RepositoryTestData.create42Puzzle());
URL url = Resources.getResource("sonia/scm/api/v2/import-repo.json");
byte[] importRequest = Resources.toByteArray(url);
MockHttpRequest request = MockHttpRequest
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "import/git/url")
.contentType(VndMediaType.REPOSITORY)
.content(importRequest);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(SC_CREATED, response.getStatus());
verify(eventBus).post(captor.capture());
assertThat(captor.getValue().isFailed()).isFalse();
}
@Test
public void shouldFailOnImportRepositoryFromUrl() throws URISyntaxException, IOException {
ArgumentCaptor<RepositoryImportEvent> captor = ArgumentCaptor.forClass(RepositoryImportEvent.class);
when(manager.getHandler("git")).thenReturn(repositoryHandler);
when(repositoryHandler.getType()).thenReturn(new RepositoryType("git", "git", ImmutableSet.of(Command.PULL)));
doThrow(ImportFailedException.class).when(manager).create(any(Repository.class), any());
URL url = Resources.getResource("sonia/scm/api/v2/import-repo.json");
byte[] importRequest = Resources.toByteArray(url);
MockHttpRequest request = MockHttpRequest
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "import/git/url")
.contentType(VndMediaType.REPOSITORY)
.content(importRequest);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(500, response.getStatus());
verify(eventBus).post(captor.capture());
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);
when(repositoryHandler.getType()).thenReturn(new RepositoryType("svn", "svn", ImmutableSet.of(Command.UNBUNDLE)));
doThrow(ImportFailedException.class).when(repositoryManager).create(any(), any());
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_INTERNAL_SERVER_ERROR, response.getStatus());
ArgumentCaptor<RepositoryImportEvent> event = ArgumentCaptor.forClass(RepositoryImportEvent.class);
verify(eventBus).post(event.capture());
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";
@@ -709,7 +482,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
MockHttpRequest request = MockHttpRequest
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/archive")
.content(new byte[]{});
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
@@ -728,7 +500,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
MockHttpRequest request = MockHttpRequest
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/unarchive")
.content(new byte[]{});
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
@@ -746,11 +517,9 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
BundleCommandBuilder bundleCommandBuilder = mock(BundleCommandBuilder.class);
when(service.getBundleCommand()).thenReturn(bundleCommandBuilder);
when(bundleCommandBuilder.getFileExtension()).thenReturn(".bundle");
MockHttpRequest request = MockHttpRequest
.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/export/svn");
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
@@ -769,11 +538,9 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
BundleCommandBuilder bundleCommandBuilder = mock(BundleCommandBuilder.class);
when(service.getBundleCommand()).thenReturn(bundleCommandBuilder);
when(bundleCommandBuilder.getFileExtension()).thenReturn(".bundle");
MockHttpRequest request = MockHttpRequest
.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/export/svn?compressed=true");
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
@@ -790,12 +557,8 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
when(manager.get(new NamespaceAndName(namespace, name))).thenReturn(repository);
mockRepositoryHandler(ImmutableSet.of(Command.BUNDLE));
BundleCommandBuilder bundleCommandBuilder = mock(BundleCommandBuilder.class);
when(service.getBundleCommand()).thenReturn(bundleCommandBuilder);
MockHttpRequest request = MockHttpRequest
.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/export/full");
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
@@ -812,9 +575,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
when(manager.get(new NamespaceAndName(namespace, name))).thenReturn(repository);
mockRepositoryHandler(ImmutableSet.of(Command.BUNDLE));
BundleCommandBuilder bundleCommandBuilder = mock(BundleCommandBuilder.class);
when(service.getBundleCommand()).thenReturn(bundleCommandBuilder);
MockHttpRequest request = MockHttpRequest
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/export/full")
.contentType(VndMediaType.REPOSITORY_EXPORT)
@@ -837,9 +597,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
when(manager.get(new NamespaceAndName(namespace, name))).thenReturn(repository);
mockRepositoryHandler(ImmutableSet.of(Command.BUNDLE));
BundleCommandBuilder bundleCommandBuilder = mock(BundleCommandBuilder.class);
when(service.getBundleCommand()).thenReturn(bundleCommandBuilder);
MockHttpRequest request = MockHttpRequest
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/export/full")
.contentType(VndMediaType.REPOSITORY_EXPORT)
@@ -860,9 +617,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
when(manager.get(new NamespaceAndName(namespace, name))).thenReturn(repository);
mockRepositoryHandler(ImmutableSet.of(Command.BUNDLE));
BundleCommandBuilder bundleCommandBuilder = mock(BundleCommandBuilder.class);
when(service.getBundleCommand()).thenReturn(bundleCommandBuilder);
when(exportService.isExporting(repository)).thenReturn(true);
MockHttpRequest request = MockHttpRequest
@@ -877,19 +631,13 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
}
@Test
public void shouldDeleteRepositoryExport() throws URISyntaxException, IOException {
public void shouldDeleteRepositoryExport() throws URISyntaxException {
String namespace = "space";
String name = "repo";
Repository repository = createRepository(namespace, name, "svn");
when(manager.get(new NamespaceAndName(namespace, name))).thenReturn(repository);
mockRepositoryHandler(ImmutableSet.of(Command.BUNDLE));
BundleCommandBuilder bundleCommandBuilder = mock(BundleCommandBuilder.class);
when(service.getBundleCommand()).thenReturn(bundleCommandBuilder);
when(exportService.isExporting(repository)).thenReturn(false);
when(exportService.getData(repository)).thenReturn(new ByteArrayInputStream("".getBytes()));
MockHttpRequest request = MockHttpRequest
.delete("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/export");
MockHttpResponse response = new MockHttpResponse();
@@ -908,9 +656,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
when(manager.get(new NamespaceAndName(namespace, name))).thenReturn(repository);
mockRepositoryHandler(ImmutableSet.of(Command.BUNDLE));
BundleCommandBuilder bundleCommandBuilder = mock(BundleCommandBuilder.class);
when(service.getBundleCommand()).thenReturn(bundleCommandBuilder);
when(exportService.isExporting(repository)).thenReturn(false);
doThrow(NotFoundException.class).when(exportService).checkExportIsAvailable(repository);
@@ -932,9 +677,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
when(manager.get(new NamespaceAndName(namespace, name))).thenReturn(repository);
mockRepositoryHandler(ImmutableSet.of(Command.BUNDLE));
BundleCommandBuilder bundleCommandBuilder = mock(BundleCommandBuilder.class);
when(service.getBundleCommand()).thenReturn(bundleCommandBuilder);
when(exportService.isExporting(repository)).thenReturn(true);
MockHttpRequest request = MockHttpRequest
@@ -954,9 +696,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
when(manager.get(new NamespaceAndName(namespace, name))).thenReturn(repository);
mockRepositoryHandler(ImmutableSet.of(Command.BUNDLE));
BundleCommandBuilder bundleCommandBuilder = mock(BundleCommandBuilder.class);
when(service.getBundleCommand()).thenReturn(bundleCommandBuilder);
when(exportService.isExporting(repository)).thenReturn(false);
when(exportService.getData(repository)).thenReturn(new ByteArrayInputStream("content".getBytes()));
when(exportService.getFileExtension(repository)).thenReturn("tar.gz.enc");
@@ -979,9 +718,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
when(manager.get(new NamespaceAndName(namespace, name))).thenReturn(repository);
mockRepositoryHandler(ImmutableSet.of(Command.BUNDLE));
BundleCommandBuilder bundleCommandBuilder = mock(BundleCommandBuilder.class);
when(service.getBundleCommand()).thenReturn(bundleCommandBuilder);
RepositoryExportInformationDto dto = new RepositoryExportInformationDto();
dto.setExporterName("trillian");
dto.setCreated(Instant.ofEpochMilli(100));
@@ -1011,7 +747,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
when(repositoryType.getSupportedCommands()).thenReturn(cmds);
}
private PageResult<Repository> createSingletonPageResult(Repository repository) {
return new PageResult<>(singletonList(repository), 0);
}
@@ -1039,48 +774,4 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
when(repositoryManager.get(id)).thenReturn(repository);
return repository;
}
/**
* This method is a slightly adapted copy of Lin Zaho's gist at https://gist.github.com/lin-zhao/9985191
*/
private void multipartRequest(MockHttpRequest request, Map<String, InputStream> files, RepositoryDto repository) throws IOException {
String boundary = UUID.randomUUID().toString();
request.contentType("multipart/form-data; boundary=" + boundary);
//Make sure this is deleted in afterTest()
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try (OutputStreamWriter formWriter = new OutputStreamWriter(buffer)) {
formWriter.append("--").append(boundary);
for (Map.Entry<String, InputStream> entry : files.entrySet()) {
formWriter.append("\n");
formWriter.append(String.format("Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"",
entry.getKey(), entry.getKey())).append("\n");
formWriter.append("Content-Type: application/octet-stream").append("\n\n");
InputStream stream = entry.getValue();
int b = stream.read();
while (b >= 0) {
formWriter.write(b);
b = stream.read();
}
stream.close();
formWriter.append("\n").append("--").append(boundary);
}
if (repository != null) {
formWriter.append("\n");
formWriter.append("Content-Disposition: form-data; name=\"repository\"").append("\n\n");
StringWriter repositoryWriter = new StringWriter();
new JsonFactory().createGenerator(repositoryWriter).setCodec(new ObjectMapper()).writeObject(repository);
formWriter.append(repositoryWriter.getBuffer().toString()).append("\n");
formWriter.append("--").append(boundary);
}
formWriter.append("--");
formWriter.flush();
}
request.setInputStream(new ByteArrayInputStream(buffer.toByteArray()));
}
}

View File

@@ -0,0 +1,186 @@
/*
* 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.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ThreadContext;
import org.junit.Assert;
import org.junit.jupiter.api.AfterEach;
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.junit.jupiter.api.io.TempDir;
import org.mockito.Answers;
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.RepositoryHandler;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryPermission;
import sonia.scm.repository.RepositoryTestData;
import sonia.scm.repository.RepositoryType;
import sonia.scm.repository.api.Command;
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.repository.work.WorkdirProvider;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Path;
import java.util.function.Consumer;
import static java.util.Collections.singleton;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow;
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 {
public static final Repository REPOSITORY = RepositoryTestData.createHeartOfGold("svn");
@Mock
private RepositoryManager manager;
@Mock
private RepositoryHandler repositoryHandler;
@Mock
private RepositoryServiceFactory serviceFactory;
@Mock
private ScmEventBus eventBus;
@Mock
private WorkdirProvider workdirProvider;
@Mock
private RepositoryImportLoggerFactory loggerFactory;
@Mock
private RepositoryImportLogger logger;
@Mock(answer = Answers.RETURNS_SELF)
private UnbundleCommandBuilder unbundleCommandBuilder;
@Mock
private Subject subject;
@InjectMocks
private FromBundleImporter importer;
@BeforeEach
void mockSubject() {
ThreadContext.bind(subject);
}
@AfterEach
void cleanupSubject() {
ThreadContext.unbindSubject();
}
@Nested
class WithPermission {
@BeforeEach
void initMocks(@TempDir Path temp) throws IOException {
when(subject.getPrincipal()).thenReturn("dent");
when(workdirProvider.createNewWorkdir(REPOSITORY.getId())).thenReturn(temp.toFile());
when(manager.create(eq(REPOSITORY), any())).thenAnswer(
invocation -> {
invocation.getArgument(1, Consumer.class).accept(REPOSITORY);
return REPOSITORY;
}
);
when(manager.getHandler("svn")).thenReturn(repositoryHandler);
RepositoryType repositoryType = mock(RepositoryType.class);
when(repositoryHandler.getType()).thenReturn(repositoryType);
when(repositoryType.getSupportedCommands()).thenReturn(singleton(Command.UNBUNDLE));
when(loggerFactory.createLogger()).thenReturn(logger);
when(unbundleCommandBuilder.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(unbundleCommandBuilder);
}
@Test
void shouldImportCompressedBundle() throws IOException {
URL dumpUrl = Resources.getResource("sonia/scm/api/v2/svn.dump.gz");
InputStream in = new ByteArrayInputStream(Resources.toByteArray(dumpUrl));
importer.importFromBundle(true, in, REPOSITORY);
verify(unbundleCommandBuilder).setCompressed(true);
verify(unbundleCommandBuilder).unbundle(any(File.class));
}
@Test
void shouldImportNonCompressedBundle() throws IOException {
URL dumpUrl = Resources.getResource("sonia/scm/api/v2/svn.dump");
InputStream in = new ByteArrayInputStream(Resources.toByteArray(dumpUrl));
importer.importFromBundle(false, in, REPOSITORY);
verify(unbundleCommandBuilder, never()).setCompressed(true);
verify(unbundleCommandBuilder).unbundle(any(File.class));
}
@Test
void shouldSetPermissionForCurrentUser() throws IOException {
URL dumpUrl = Resources.getResource("sonia/scm/api/v2/svn.dump");
InputStream in = new ByteArrayInputStream(Resources.toByteArray(dumpUrl));
Repository createdRepository = importer.importFromBundle(false, in, REPOSITORY);
assertThat(createdRepository.getPermissions())
.hasSize(1);
RepositoryPermission permission = createdRepository.getPermissions().iterator().next();
assertThat(permission.getName()).isEqualTo("dent");
assertThat(permission.isGroupPermission()).isFalse();
assertThat(permission.getRole()).isEqualTo("OWNER");
}
}
@Test
void shouldFailWithoutPermission() throws IOException {
URL dumpUrl = Resources.getResource("sonia/scm/api/v2/svn.dump");
InputStream in = new ByteArrayInputStream(Resources.toByteArray(dumpUrl));
doThrow(new AuthorizationException()).when(subject).checkPermission("repository:create");
assertThrows(AuthorizationException.class, () -> importer.importFromBundle(false, in, REPOSITORY));
verify(manager, never()).create(any(), any());
}
}

View File

@@ -0,0 +1,186 @@
/*
* 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.apache.shiro.subject.Subject;
import org.apache.shiro.util.ThreadContext;
import org.junit.jupiter.api.AfterEach;
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.RepositoryHandler;
import sonia.scm.repository.RepositoryImportEvent;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryTestData;
import sonia.scm.repository.RepositoryType;
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 java.util.Collections.singleton;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
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;
import static sonia.scm.repository.api.Command.PULL;
@ExtendWith(MockitoExtension.class)
class FromUrlImporterTest {
@Mock
private RepositoryManager manager;
@Mock
private RepositoryServiceFactory serviceFactory;
@Mock
private RepositoryService service;
@Mock
private ScmEventBus eventBus;
@Mock
private RepositoryImportLoggerFactory loggerFactory;
@Mock
private RepositoryImportLogger logger;
@Mock
private Subject subject;
@InjectMocks
private FromUrlImporter importer;
private final Repository repository = RepositoryTestData.createHeartOfGold("git");
@BeforeEach
void setUpMocks() {
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
when(loggerFactory.createLogger()).thenReturn(logger);
when(manager.create(any(), any())).thenAnswer(
invocation -> {
Repository repository = invocation.getArgument(0, Repository.class);
Repository createdRepository = repository.clone();
createdRepository.setNamespace("created");
invocation.getArgument(1, Consumer.class).accept(createdRepository);
return createdRepository;
}
);
}
@BeforeEach
void setUpRepositoryType() {
RepositoryHandler repositoryHandler = mock(RepositoryHandler.class);
when(manager.getHandler(repository.getType())).thenReturn(repositoryHandler);
RepositoryType repositoryType = mock(RepositoryType.class);
when(repositoryHandler.getType()).thenReturn(repositoryType);
when(repositoryType.getSupportedCommands()).thenReturn(singleton(PULL));
}
@BeforeEach
void mockSubject() {
ThreadContext.bind(subject);
when(subject.getPrincipal()).thenReturn("trillian");
}
@AfterEach
void cleanupSubject() {
ThreadContext.unbindSubject();
}
@Test
void shouldPullChangesFromRemoteUrl() throws IOException {
PullCommandBuilder pullCommandBuilder = mock(PullCommandBuilder.class, RETURNS_SELF);
when(service.getPullCommand()).thenReturn(pullCommandBuilder);
FromUrlImporter.RepositoryImportParameters parameters = new FromUrlImporter.RepositoryImportParameters();
parameters.setImportUrl("https://scm-manager.org/scm/repo/scmadmin/scm-manager.git");
Repository createdRepository = importer.importFromUrl(parameters, repository);
assertThat(createdRepository.getNamespace()).isEqualTo("created");
verify(pullCommandBuilder).pull("https://scm-manager.org/scm/repo/scmadmin/scm-manager.git");
verify(logger).finished();
verify(eventBus).post(argThat(
event -> {
assertThat(event).isInstanceOf(RepositoryImportEvent.class);
RepositoryImportEvent repositoryImportEvent = (RepositoryImportEvent) event;
assertThat(repositoryImportEvent.getItem().getNamespace()).isEqualTo("created");
assertThat(repositoryImportEvent.isFailed()).isFalse();
return true;
}
));
}
@Test
void shouldPullChangesFromRemoteUrlWithCredentials() {
PullCommandBuilder pullCommandBuilder = mock(PullCommandBuilder.class, RETURNS_SELF);
when(service.getPullCommand()).thenReturn(pullCommandBuilder);
FromUrlImporter.RepositoryImportParameters parameters = new FromUrlImporter.RepositoryImportParameters();
parameters.setImportUrl("https://scm-manager.org/scm/repo/scmadmin/scm-manager.git");
parameters.setUsername("trillian");
parameters.setPassword("secret");
importer.importFromUrl(parameters, 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(TestException.class).when(pullCommandBuilder).pull(anyString());
when(logger.started()).thenReturn(true);
FromUrlImporter.RepositoryImportParameters parameters = new FromUrlImporter.RepositoryImportParameters();
parameters.setImportUrl("https://scm-manager.org/scm/repo/scmadmin/scm-manager.git");
assertThrows(ImportFailedException.class, () -> importer.importFromUrl(parameters, repository));
verify(logger).failed(argThat(e -> e instanceof TestException));
verify(eventBus).post(argThat(
event -> {
assertThat(event).isInstanceOf(RepositoryImportEvent.class);
RepositoryImportEvent repositoryImportEvent = (RepositoryImportEvent) event;
assertThat(repositoryImportEvent.getItem()).isEqualTo(repository);
assertThat(repositoryImportEvent.isFailed()).isTrue();
return true;
}
));
}
private static class TestException extends RuntimeException {}
}

View File

@@ -25,6 +25,9 @@
package sonia.scm.importexport;
import com.google.common.io.Resources;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ThreadContext;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@@ -40,6 +43,7 @@ import sonia.scm.event.ScmEventBus;
import sonia.scm.repository.ImportRepositoryHookEvent;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryHookEvent;
import sonia.scm.repository.RepositoryImportEvent;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryPermission;
import sonia.scm.repository.RepositoryTestData;
@@ -64,6 +68,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -94,6 +100,14 @@ class FullScmRepositoryImporterTest {
private RepositoryImportExportEncryption repositoryImportExportEncryption;
@Mock
private WorkdirProvider workdirProvider;
@Mock
private RepositoryImportLogger logger;
@Mock
private RepositoryImportLoggerFactory loggerFactory;
@Mock
private Subject subject;
@Mock
private ScmEventBus eventBus;
@InjectMocks
private EnvironmentCheckStep environmentCheckStep;
@@ -104,9 +118,6 @@ class FullScmRepositoryImporterTest {
@InjectMocks
private RepositoryImportStep repositoryImportStep;
@Mock
private ScmEventBus eventBus;
@Mock
private RepositoryHookEvent event;
@@ -124,14 +135,25 @@ class FullScmRepositoryImporterTest {
repositoryImportStep,
repositoryManager,
repositoryImportExportEncryption,
eventBus
);
loggerFactory,
eventBus);
}
@BeforeEach
void initRepositoryService() {
lenient().when(serviceFactory.create(REPOSITORY)).thenReturn(service);
lenient().when(service.getUnbundleCommand()).thenReturn(unbundleCommandBuilder);
lenient().when(loggerFactory.createLogger()).thenReturn(logger);
}
@BeforeEach
void initSubject() {
ThreadContext.bind(subject);
}
@BeforeEach
void cleanupSubject() {
ThreadContext.unbindSubject();
}
@Test
@@ -154,6 +176,28 @@ class FullScmRepositoryImporterTest {
IncompatibleEnvironmentForImportException.class,
() -> fullImporter.importFromStream(REPOSITORY, importStream, "")
);
verify(eventBus).post(argThat(
event -> {
assertThat(event).isInstanceOf(RepositoryImportEvent.class);
RepositoryImportEvent repositoryImportEvent = (RepositoryImportEvent) event;
assertThat(repositoryImportEvent.getItem()).isEqualTo(REPOSITORY);
assertThat(repositoryImportEvent.isFailed()).isTrue();
return true;
}
));
}
@Test
void shouldNotImportRepositoryWithoutPermission() throws IOException {
doThrow(AuthorizationException.class).when(subject).checkPermission("repository:create");
InputStream stream = Resources.getResource("sonia/scm/repository/import/scm-import.tar.gz").openStream();
assertThrows(AuthorizationException.class, () -> fullImporter.importFromStream(REPOSITORY, stream, null));
verify(storeImporter, never()).importFromTarArchive(any(Repository.class), any(InputStream.class), any(RepositoryImportLogger.class));
verify(repositoryManager, never()).modify(any());
}
@Nested
@@ -174,7 +218,7 @@ class FullScmRepositoryImporterTest {
Repository repository = fullImporter.importFromStream(REPOSITORY, stream, "");
assertThat(repository).isEqualTo(REPOSITORY);
verify(storeImporter).importFromTarArchive(eq(REPOSITORY), any(InputStream.class));
verify(storeImporter).importFromTarArchive(eq(REPOSITORY), any(InputStream.class), eq(logger));
verify(repositoryManager).modify(REPOSITORY);
Collection<RepositoryPermission> updatedPermissions = REPOSITORY.getPermissions();
assertThat(updatedPermissions).hasSize(2);
@@ -192,6 +236,33 @@ class FullScmRepositoryImporterTest {
assertThat(workDirExists).isFalse();
}
@Test
void shouldSendImportedEventForImportedRepository() throws IOException {
InputStream stream = Resources.getResource("sonia/scm/repository/import/scm-import.tar.gz").openStream();
when(unbundleCommandBuilder.setPostEventSink(any())).thenAnswer(
invocation -> {
invocation.getArgument(0, Consumer.class).accept(new RepositoryHookEvent(null, REPOSITORY, null));
return null;
}
);
ArgumentCaptor<Object> capturedEvents = ArgumentCaptor.forClass(Object.class);
doNothing().when(eventBus).post(capturedEvents.capture());
fullImporter.importFromStream(REPOSITORY, stream, null);
assertThat(capturedEvents.getAllValues()).hasSize(2);
assertThat(capturedEvents.getAllValues()).anyMatch(
event ->
event instanceof ImportRepositoryHookEvent &&
((ImportRepositoryHookEvent) event).getRepository().equals(REPOSITORY)
);
assertThat(capturedEvents.getAllValues()).anyMatch(
event ->
event instanceof RepositoryImportEvent &&
((RepositoryImportEvent) event).getItem().equals(REPOSITORY)
);
}
@Test
void shouldTriggerUpdateForImportedRepository() throws IOException {
InputStream stream = Resources.getResource("sonia/scm/repository/import/scm-import.tar.gz").openStream();
@@ -207,7 +278,8 @@ class FullScmRepositoryImporterTest {
Repository repository = fullImporter.importFromStream(REPOSITORY, stream, "");
assertThat(repository).isEqualTo(REPOSITORY);
verify(storeImporter).importFromTarArchive(eq(REPOSITORY), any(InputStream.class));
verify(storeImporter).importFromTarArchive(eq(REPOSITORY), any(InputStream.class), eq(logger));
verify(repositoryManager).create(REPOSITORY);
verify(repositoryManager).modify(REPOSITORY);
verify(unbundleCommandBuilder).unbundle((InputStream) argThat(argument -> argument.getClass().equals(NoneClosingInputStream.class)));
verify(workdirProvider, never()).createNewWorkdir(REPOSITORY.getId());

View File

@@ -0,0 +1,136 @@
/*
* 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.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ThreadContext;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import sonia.scm.NotFoundException;
import sonia.scm.store.Blob;
import sonia.scm.store.InMemoryBlobStore;
import sonia.scm.store.InMemoryBlobStoreFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
class RepositoryImportLoggerFactoryTest {
private final Subject subject = mock(Subject.class);
private final InMemoryBlobStore store = new InMemoryBlobStore();
private final RepositoryImportLoggerFactory factory = new RepositoryImportLoggerFactory(new InMemoryBlobStoreFactory(store));
@BeforeEach
void initSubject() {
ThreadContext.bind(subject);
}
@AfterEach
void cleanupSubject() {
ThreadContext.unbindSubject();
}
@Test
void shouldReadLogForExportingUser() throws IOException {
when(subject.getPrincipal()).thenReturn("dent");
createLog();
ByteArrayOutputStream out = new ByteArrayOutputStream();
factory.getLog("42", out);
assertLogReadCorrectly(out);
}
@Test
void shouldReadLogForAdmin() throws IOException {
when(subject.getPrincipal()).thenReturn("trillian");
when(subject.isPermitted(anyString())).thenReturn(true);
createLog();
ByteArrayOutputStream out = new ByteArrayOutputStream();
factory.getLog("42", out);
assertLogReadCorrectly(out);
}
private void assertLogReadCorrectly(ByteArrayOutputStream out) {
assertThat(out).asString().contains(
"Import of repository hitchhiker/HeartOfGold",
"Repository type: git",
"Imported from: URL",
"Imported by dent (Arthur Dent)",
"",
"Thu Feb 25 11:11:07 CET 2021 - import started",
"Thu Feb 25 11:11:07 CET 2021 - pulling repository from https://github.com/scm-manager/scm-manager",
"Thu Feb 25 11:11:08 CET 2021 - import finished successfully"
);
}
@Test
void shouldThrowNotFoundExceptionForMissingLog() {
ByteArrayOutputStream out = new ByteArrayOutputStream();
assertThrows(NotFoundException.class, () -> factory.getLog("42", out));
}
@Test
void shouldFailWithoutPermission() throws IOException {
when(subject.getPrincipal()).thenReturn("trillian");
createLog();
doThrow(AuthorizationException.class).when(subject).checkPermission("only:admin:allowed");
ByteArrayOutputStream out = new ByteArrayOutputStream();
assertThrows(AuthorizationException.class, () -> factory.getLog("42", out));
}
@SuppressWarnings("UnstableApiUsage")
private void createLog() throws IOException {
Blob blob = store.create("42");
try (OutputStream outputStream = blob.getOutputStream()) {
Resources.copy(
Resources.getResource("sonia/scm/importexport/importLog.blob"),
outputStream);
}
blob.commit();
}
}

View File

@@ -53,6 +53,8 @@ class TarArchiveRepositoryStoreImporterTest {
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private RepositoryStoreImporter repositoryStoreImporter;
@Mock
private RepositoryImportLogger logger;
@InjectMocks
private TarArchiveRepositoryStoreImporter tarArchiveRepositoryStoreImporter;
@@ -60,20 +62,20 @@ class TarArchiveRepositoryStoreImporterTest {
@Test
void shouldDoNothingIfNoEntries() {
ByteArrayInputStream bais = new ByteArrayInputStream("".getBytes());
tarArchiveRepositoryStoreImporter.importFromTarArchive(repository, bais);
tarArchiveRepositoryStoreImporter.importFromTarArchive(repository, bais, logger);
verify(repositoryStoreImporter, never()).doImport(any(Repository.class));
}
@Test
void shouldImportEachEntry() throws IOException {
InputStream inputStream = Resources.getResource("sonia/scm/repository/import/scm-metadata.tar").openStream();
tarArchiveRepositoryStoreImporter.importFromTarArchive(repository, inputStream);
tarArchiveRepositoryStoreImporter.importFromTarArchive(repository, inputStream, logger);
verify(repositoryStoreImporter, times(2)).doImport(repository);
}
@Test
void shouldThrowImportFailedExceptionIfInvalidStorePath() throws IOException {
InputStream inputStream = Resources.getResource("sonia/scm/repository/import/scm-metadata_invalid.tar").openStream();
assertThrows(ImportFailedException.class, () -> tarArchiveRepositoryStoreImporter.importFromTarArchive(repository, inputStream));
assertThrows(ImportFailedException.class, () -> tarArchiveRepositoryStoreImporter.importFromTarArchive(repository, inputStream, logger));
}
}