mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-02-21 22:16:55 +01:00
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:
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user