Merge with 2.0.0-m3

This commit is contained in:
René Pfeuffer
2018-08-30 11:48:58 +02:00
26 changed files with 789 additions and 49 deletions

View File

@@ -26,6 +26,7 @@ public class VndMediaType {
public static final String REPOSITORY_TYPE_COLLECTION = PREFIX + "repositoryTypeCollection" + SUFFIX;
public static final String REPOSITORY_TYPE = PREFIX + "repositoryType" + SUFFIX;
public static final String ME = PREFIX + "me" + SUFFIX;
public static final String SOURCE = PREFIX + "source" + SUFFIX;
private VndMediaType() {
}

View File

@@ -15,6 +15,8 @@ import java.io.File;
import java.io.IOException;
import java.util.Collection;
import static java.lang.Thread.sleep;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertNotNull;
import static sonia.scm.it.RestUtil.given;
import static sonia.scm.it.ScmTypes.availableScmTypes;
@@ -69,4 +71,57 @@ public class RepositoryAccessITCase {
assertNotNull(branchName);
}
@Test
public void shouldReadContent() throws IOException, InterruptedException {
RepositoryClient repositoryClient = RepositoryUtil.createRepositoryClient(repositoryType, folder);
RepositoryUtil.createAndCommitFile(repositoryClient, "scmadmin", "a.txt", "a");
tempFolder.newFolder("subfolder");
RepositoryUtil.createAndCommitFile(repositoryClient, "scmadmin", "subfolder/a.txt", "sub-a");
sleep(1000);
String sourcesUrl = given()
.when()
.get(TestData.getDefaultRepositoryUrl(repositoryType))
.then()
.statusCode(HttpStatus.SC_OK)
.extract()
.path("_links.sources.href");
String rootContentUrl = given()
.when()
.get(sourcesUrl)
.then()
.statusCode(HttpStatus.SC_OK)
.extract()
.path("files.find{it.name=='a.txt'}._links.self.href");
given()
.when()
.get(rootContentUrl)
.then()
.statusCode(HttpStatus.SC_OK)
.body(equalTo("a"));
String subfolderSourceUrl = given()
.when()
.get(sourcesUrl)
.then()
.statusCode(HttpStatus.SC_OK)
.extract()
.path("files.find{it.name=='subfolder'}._links.self.href");
String subfolderContentUrl= given()
.when()
.get(subfolderSourceUrl)
.then()
.statusCode(HttpStatus.SC_OK)
.extract()
.path("files[0]._links.self.href");
given()
.when()
.get(subfolderContentUrl)
.then()
.statusCode(HttpStatus.SC_OK)
.body(equalTo("sub-a"));
}
}

View File

@@ -37,11 +37,26 @@ public class RepositoryUtil {
}
static void createAndCommitFile(RepositoryClient repositoryClient, String username, String fileName, String content) throws IOException {
Files.write(content, new File(repositoryClient.getWorkingCopy(), fileName), Charsets.UTF_8);
repositoryClient.getAddCommand().add(fileName);
File file = new File(repositoryClient.getWorkingCopy(), fileName);
Files.write(content, file, Charsets.UTF_8);
addWithParentDirectories(repositoryClient, file);
commit(repositoryClient, username, "added " + fileName);
}
private static String addWithParentDirectories(RepositoryClient repositoryClient, File file) throws IOException {
File parent = file.getParentFile();
String thisName = file.getName();
String path;
if (!repositoryClient.getWorkingCopy().equals(parent)) {
addWithParentDirectories(repositoryClient, parent);
path = addWithParentDirectories(repositoryClient, parent) + File.separator + thisName;
} else {
path = thisName;
}
repositoryClient.getAddCommand().add(path);
return path;
}
static Changeset commit(RepositoryClient repositoryClient, String username, String message) throws IOException {
Changeset changeset = repositoryClient.getCommitCommand().commit(new Person(username, username + "@scm-manager.org"), message);
if (repositoryClient.isCommandSupported(ClientCommand.PUSH)) {

View File

@@ -66,9 +66,7 @@ public class HgBrowseCommand extends AbstractCommand implements BrowseCommand
//~--- get methods ----------------------------------------------------------
@Override
public BrowserResult getBrowserResult(BrowseCommandRequest request)
throws IOException
{
public BrowserResult getBrowserResult(BrowseCommandRequest request) throws IOException {
HgFileviewCommand cmd = HgFileviewCommand.on(open());
if (!Strings.isNullOrEmpty(request.getRevision()))
@@ -100,6 +98,12 @@ public class HgBrowseCommand extends AbstractCommand implements BrowseCommand
result.setFiles(cmd.execute());
if (!Strings.isNullOrEmpty(request.getRevision())) {
result.setRevision(request.getRevision());
} else {
result.setRevision("tip");
}
return result;
}
}

View File

@@ -347,6 +347,7 @@ public final class SvnUtil
}
public static long getRevisionNumber(String revision) throws RevisionNotFoundException {
// REVIEW Bei SVN wird ohne Revision die -1 genommen, was zu einem Fehler führt
long revisionNumber = -1;
if (Util.isNotEmpty(revision))

View File

@@ -112,6 +112,10 @@ public class SvnBrowseCommand extends AbstractSvnCommand
}
}
if (revisionNumber == -1) {
revisionNumber = svnRepository.getLatestRevision();
}
result = new BrowserResult();
result.setRevision(String.valueOf(revisionNumber));
result.setFiles(children);

View File

@@ -29,7 +29,7 @@ public abstract class BranchToBranchDtoMapper {
.self(resourceLinks.branch().self(namespaceAndName, target.getName()))
.single(linkBuilder("history", resourceLinks.branch().history(namespaceAndName, target.getName())).build())
.single(linkBuilder("changeset", resourceLinks.changeset().changeset(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())).build())
.single(linkBuilder("source", resourceLinks.source().source(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())).build());
.single(linkBuilder("source", resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())).build());
target.add(linksBuilder.build());
}
}

View File

@@ -0,0 +1,40 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.Iterator;
import java.util.List;
@Getter
@Setter
@NoArgsConstructor
public class BrowserResultDto extends HalRepresentation implements Iterable<FileObjectDto> {
private String revision;
private String tag;
private String branch;
// REVIEW files nicht embedded?
private List<FileObjectDto> files;
@Override
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
protected HalRepresentation add(Links links) {
return super.add(links);
}
// REVIEW return null?
@Override
public Iterator<FileObjectDto> iterator() {
Iterator<FileObjectDto> it = null;
if (files != null)
{
it = files.iterator();
}
return it;
}
}

View File

@@ -0,0 +1,50 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Links;
import sonia.scm.repository.BrowserResult;
import sonia.scm.repository.FileObject;
import sonia.scm.repository.NamespaceAndName;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;
public class BrowserResultToBrowserResultDtoMapper {
@Inject
private FileObjectToFileObjectDtoMapper fileObjectToFileObjectDtoMapper;
@Inject
private ResourceLinks resourceLinks;
public BrowserResultDto map(BrowserResult browserResult, NamespaceAndName namespaceAndName) {
BrowserResultDto browserResultDto = new BrowserResultDto();
browserResultDto.setTag(browserResult.getTag());
browserResultDto.setBranch(browserResult.getBranch());
browserResultDto.setRevision(browserResult.getRevision());
List<FileObjectDto> fileObjectDtoList = new ArrayList<>();
for (FileObject fileObject : browserResult.getFiles()) {
fileObjectDtoList.add(mapFileObject(fileObject, namespaceAndName, browserResult.getRevision()));
}
browserResultDto.setFiles(fileObjectDtoList);
this.addLinks(browserResult, browserResultDto, namespaceAndName);
return browserResultDto;
}
private FileObjectDto mapFileObject(FileObject fileObject, NamespaceAndName namespaceAndName, String revision) {
return fileObjectToFileObjectDtoMapper.map(fileObject, namespaceAndName, revision);
}
private void addLinks(BrowserResult browserResult, BrowserResultDto dto, NamespaceAndName namespaceAndName) {
if (browserResult.getRevision() == null) {
dto.add(Links.linkingTo().self(resourceLinks.source().selfWithoutRevision(namespaceAndName.getNamespace(), namespaceAndName.getName())).build());
} else {
dto.add(Links.linkingTo().self(resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), browserResult.getRevision())).build());
}
}
}

View File

@@ -60,8 +60,8 @@ public class ContentResource {
@ResponseCode(code = 500, condition = "internal server error")
})
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision, @PathParam("path") String path) {
StreamingOutput stream = createStreamingOutput(namespace, name, revision, path);
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
StreamingOutput stream = createStreamingOutput(namespace, name, revision, path, repositoryService);
Response.ResponseBuilder responseBuilder = Response.ok(stream);
return createContentHeader(namespace, name, revision, path, repositoryService, responseBuilder);
} catch (RepositoryNotFoundException e) {
@@ -70,11 +70,14 @@ public class ContentResource {
}
}
private StreamingOutput createStreamingOutput(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision, @PathParam("path") String path, RepositoryService repositoryService) {
private StreamingOutput createStreamingOutput(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision, @PathParam("path") String path) {
return os -> {
try {
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
repositoryService.getCatCommand().setRevision(revision).retriveContent(os, path);
os.close();
} catch (RepositoryNotFoundException e) {
LOG.debug("repository {}/{} not found", path, namespace, name, e);
throw new WebApplicationException(Status.NOT_FOUND);
} catch (PathNotFoundException e) {
LOG.debug("path '{}' not found in repository {}/{}", path, namespace, name, e);
throw new WebApplicationException(Status.NOT_FOUND);
@@ -141,7 +144,9 @@ public class ContentResource {
try {
byte[] buffer = new byte[HEAD_BUFFER_SIZE];
int length = stream.read(buffer);
if (length < buffer.length) {
if (length < 0) { // empty file
return new byte[]{};
} else if (length < buffer.length) {
return Arrays.copyOf(buffer, length);
} else {
return buffer;

View File

@@ -0,0 +1,28 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.time.Instant;
@Getter
@Setter
@NoArgsConstructor
public class FileObjectDto extends HalRepresentation {
private String name;
private String path;
private boolean directory;
private String description;
private int length;
private Instant lastModified;
private SubRepositoryDto subRepository;
@Override
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
protected HalRepresentation add(Links links) {
return super.add(links);
}
}

View File

@@ -0,0 +1,46 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Links;
import org.mapstruct.AfterMapping;
import org.mapstruct.Context;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import sonia.scm.repository.FileObject;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.SubRepository;
import javax.inject.Inject;
import java.net.URI;
@Mapper
public abstract class FileObjectToFileObjectDtoMapper extends BaseMapper<FileObject, FileObjectDto> {
@Inject
private ResourceLinks resourceLinks;
protected abstract FileObjectDto map(FileObject fileObject, @Context NamespaceAndName namespaceAndName, @Context String revision);
abstract SubRepositoryDto mapSubrepository(SubRepository subRepository);
@AfterMapping
void addLinks(FileObject fileObject, @MappingTarget FileObjectDto dto, @Context NamespaceAndName namespaceAndName, @Context String revision) {
String path = removeFirstSlash(fileObject.getPath());
Links.Builder links = Links.linkingTo();
if (dto.isDirectory()) {
links.self(addPath(resourceLinks.source().sourceWithPath(namespaceAndName.getNamespace(), namespaceAndName.getName(), revision, ""), path));
} else {
links.self(addPath(resourceLinks.source().content(namespaceAndName.getNamespace(), namespaceAndName.getName(), revision, ""), path));
}
dto.add(links.build());
}
// we have to add the file path using URI, so that path separators (aka '/') will not be encoded as '%2F'
private String addPath(String sourceWithPath, String path) {
return URI.create(sourceWithPath).resolve(path).toASCIIString();
}
private String removeFirstSlash(String source) {
return source.startsWith("/") ? source.substring(1) : source;
}
}

View File

@@ -28,6 +28,8 @@ public class MapperModule extends AbstractModule {
bind(PermissionDtoToPermissionMapper.class).to(Mappers.getMapper(PermissionDtoToPermissionMapper.class).getClass());
bind(PermissionToPermissionDtoMapper.class).to(Mappers.getMapper(PermissionToPermissionDtoMapper.class).getClass());
bind(FileObjectToFileObjectDtoMapper.class).to(Mappers.getMapper(FileObjectToFileObjectDtoMapper.class).getClass());
bind(UriInfoStore.class).in(ServletScopes.REQUEST);
}
}

View File

@@ -47,7 +47,7 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper<Reposit
}
}
linksBuilder.single(link("changesets", resourceLinks.changeset().self(target.getNamespace(), target.getName())));
linksBuilder.single(link("sources", resourceLinks.source().self(target.getNamespace(), target.getName())));
linksBuilder.single(link("sources", resourceLinks.source().selfWithoutRevision(target.getNamespace(), target.getName())));
target.add(linksBuilder.build());
}
}

View File

@@ -290,13 +290,29 @@ class ResourceLinks {
sourceLinkBuilder = new LinkBuilder(uriInfo, RepositoryRootResource.class, RepositoryResource.class, SourceRootResource.class);
}
String self(String namespace, String name) {
return sourceLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("sources").parameters().method("getAll").parameters().href();
String self(String namespace, String name, String revision) {
return sourceLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("sources").parameters().method("getAll").parameters(revision).href();
}
String selfWithoutRevision(String namespace, String name) {
return sourceLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("sources").parameters().method("getAllWithoutRevision").parameters().href();
}
public String source(String namespace, String name, String revision) {
return sourceLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("sources").parameters().method("get").parameters(revision).href();
}
public String sourceWithPath(String namespace, String name, String revision, String path) {
if (revision == null) {
return sourceLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("sources").parameters().method("get").parameters(null, path).href();
} else {
return sourceLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("sources").parameters().method("get").parameters(revision, path).href();
}
}
public String content(String namespace, String name, String revision, String path) {
return sourceLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("content").parameters().method("get").parameters(revision, path).href();
}
}
public PermissionLinks permission() {
return new PermissionLinks(uriInfoStore.get());

View File

@@ -1,25 +1,75 @@
package sonia.scm.api.v2.resources;
import javax.ws.rs.DefaultValue;
import sonia.scm.repository.BrowserResult;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.RepositoryNotFoundException;
import sonia.scm.repository.RevisionNotFoundException;
import sonia.scm.repository.api.BrowseCommandBuilder;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import java.io.IOException;
public class SourceRootResource {
@GET
@Path("")
public Response getAll(@DefaultValue("0") @QueryParam("page") int page,
@DefaultValue("10") @QueryParam("pageSize") int pageSize,
@QueryParam("sortBy") String sortBy,
@DefaultValue("false") @QueryParam("desc") boolean desc) {
throw new UnsupportedOperationException();
private final RepositoryServiceFactory serviceFactory;
private final BrowserResultToBrowserResultDtoMapper browserResultToBrowserResultDtoMapper;
@Inject
public SourceRootResource(RepositoryServiceFactory serviceFactory, BrowserResultToBrowserResultDtoMapper browserResultToBrowserResultDtoMapper) {
this.serviceFactory = serviceFactory;
this.browserResultToBrowserResultDtoMapper = browserResultToBrowserResultDtoMapper;
}
@GET
@Produces(VndMediaType.SOURCE)
@Path("")
public Response getAllWithoutRevision(@PathParam("namespace") String namespace, @PathParam("name") String name) {
return getSource(namespace, name, "/", null);
}
@GET
@Produces(VndMediaType.SOURCE)
@Path("{revision}")
public Response get() {
throw new UnsupportedOperationException();
public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision) {
return getSource(namespace, name, "/", revision);
}
@GET
@Produces(VndMediaType.SOURCE)
@Path("{revision}/{path: .*}")
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision, @PathParam("path") String path) {
return getSource(namespace, name, path, revision);
}
private Response getSource(String namespace, String repoName, String path, String revision) {
NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, repoName);
try (RepositoryService repositoryService = serviceFactory.create(namespaceAndName)) {
BrowseCommandBuilder browseCommand = repositoryService.getBrowseCommand();
browseCommand.setPath(path);
if (revision != null && !revision.isEmpty()) {
browseCommand.setRevision(revision);
}
BrowserResult browserResult = browseCommand.getBrowserResult();
if (browserResult != null) {
return Response.ok(browserResultToBrowserResultDtoMapper.map(browserResult, namespaceAndName)).build();
} else {
return Response.status(Response.Status.NOT_FOUND).build();
}
} catch (RepositoryNotFoundException | RevisionNotFoundException e) {
return Response.status(Response.Status.NOT_FOUND).build();
} catch (IOException e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
}
}
}

View File

@@ -0,0 +1,14 @@
package sonia.scm.api.v2.resources;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
public class SubRepositoryDto {
private String repositoryUrl;
private String browserUrl;
private String revision;
}

View File

@@ -0,0 +1,114 @@
package sonia.scm.api.v2.resources;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.support.SubjectThreadState;
import org.apache.shiro.util.ThreadContext;
import org.apache.shiro.util.ThreadState;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import sonia.scm.repository.BrowserResult;
import sonia.scm.repository.FileObject;
import sonia.scm.repository.NamespaceAndName;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.MockitoAnnotations.initMocks;
public class BrowserResultToBrowserResultDtoMapperTest {
private final URI baseUri = URI.create("http://example.com/base/");
@SuppressWarnings("unused") // Is injected
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
@Mock
private FileObjectToFileObjectDtoMapper fileObjectToFileObjectDtoMapper;
@InjectMocks
private BrowserResultToBrowserResultDtoMapper mapper;
private final Subject subject = mock(Subject.class);
private final ThreadState subjectThreadState = new SubjectThreadState(subject);
private FileObject fileObject1 = new FileObject();
private FileObject fileObject2 = new FileObject();
@Before
public void init() {
initMocks(this);
subjectThreadState.bind();
ThreadContext.bind(subject);
fileObject1.setName("FO 1");
fileObject1.setLength(100);
fileObject1.setLastModified(0L);
fileObject1.setPath("/path/object/1");
fileObject1.setDescription("description of file object 1");
fileObject1.setDirectory(false);
fileObject2.setName("FO 2");
fileObject2.setLength(100);
fileObject2.setLastModified(101L);
fileObject2.setPath("/path/object/2");
fileObject2.setDescription("description of file object 2");
fileObject2.setDirectory(true);
}
@After
public void unbind() {
ThreadContext.unbindSubject();
}
@Test
public void shouldMapAttributesCorrectly() {
BrowserResult browserResult = createBrowserResult();
BrowserResultDto dto = mapper.map(browserResult, new NamespaceAndName("foo", "bar"));
assertEqualAttributes(browserResult, dto);
}
@Test
public void shouldDelegateToFileObjectsMapper() {
BrowserResult browserResult = createBrowserResult();
NamespaceAndName namespaceAndName = new NamespaceAndName("foo", "bar");
BrowserResultDto dto = mapper.map(browserResult, namespaceAndName);
verify(fileObjectToFileObjectDtoMapper).map(fileObject1, namespaceAndName, "Revision");
verify(fileObjectToFileObjectDtoMapper).map(fileObject2, namespaceAndName, "Revision");
}
private BrowserResult createBrowserResult() {
BrowserResult browserResult = new BrowserResult();
browserResult.setTag("Tag");
browserResult.setRevision("Revision");
browserResult.setBranch("Branch");
browserResult.setFiles(createFileObjects());
return browserResult;
}
private List<FileObject> createFileObjects() {
List<FileObject> fileObjects = new ArrayList<>();
fileObjects.add(fileObject1);
fileObjects.add(fileObject2);
return fileObjects;
}
private void assertEqualAttributes(BrowserResult browserResult, BrowserResultDto dto) {
assertThat(dto.getTag()).isEqualTo(browserResult.getTag());
assertThat(dto.getBranch()).isEqualTo(browserResult.getBranch());
assertThat(dto.getRevision()).isEqualTo(browserResult.getRevision());
}
}

View File

@@ -3,6 +3,7 @@ package sonia.scm.api.v2.resources;
import com.github.sdorra.shiro.ShiroRule;
import com.github.sdorra.shiro.SubjectAware;
import com.google.common.io.Resources;
import org.apache.shiro.util.ThreadContext;
import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.mock.MockDispatcherFactory;
import org.jboss.resteasy.mock.MockHttpRequest;
@@ -21,7 +22,9 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.MockitoAnnotations.initMocks;
@SubjectAware(
@@ -47,6 +50,13 @@ public class ConfigResourceTest {
@InjectMocks
private ScmConfigurationToConfigDtoMapperImpl configToDtoMapper;
public ConfigResourceTest() {
// cleanup state that might have been left by other tests
ThreadContext.unbindSecurityManager();
ThreadContext.unbindSubject();
ThreadContext.remove();
}
@Before
public void prepareEnvironment() {
initMocks(this);

View File

@@ -108,17 +108,6 @@ public class ContentResourceTest {
assertEquals("text/plain", response.getHeaderString("Content-Type"));
}
@Test
public void shouldRecognizeShebangSourceCode() throws Exception {
mockContentFromResource("someScript.sh");
Response response = contentResource.get(NAMESPACE, REPO_NAME, REV, "someScript.sh");
assertEquals(200, response.getStatus());
assertEquals("PYTHON", response.getHeaderString("Language"));
assertEquals("application/x-sh", response.getHeaderString("Content-Type"));
}
@Test
public void shouldHandleRandomByteFile() throws Exception {
mockContentFromResource("JustBytes");
@@ -142,6 +131,17 @@ public class ContentResourceTest {
assertTrue("stream has to be closed after reading head", stream.isClosed());
}
@Test
public void shouldHandleEmptyFile() throws Exception {
mockContent("empty", new byte[]{});
Response response = contentResource.get(NAMESPACE, REPO_NAME, REV, "empty");
assertEquals(200, response.getStatus());
assertFalse(response.getHeaders().containsKey("Language"));
assertEquals("application/octet-stream", response.getHeaderString("Content-Type"));
}
private void mockContentFromResource(String fileName) throws Exception {
URL url = Resources.getResource(fileName);
mockContent(fileName, Resources.toByteArray(url));

View File

@@ -0,0 +1,102 @@
package sonia.scm.api.v2.resources;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.support.SubjectThreadState;
import org.apache.shiro.util.ThreadContext;
import org.apache.shiro.util.ThreadState;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.FileObject;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.SubRepository;
import java.net.URI;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@RunWith(MockitoJUnitRunner.Silent.class)
public class FileObjectToFileObjectDtoMapperTest {
private final URI baseUri = URI.create("http://example.com/base/");
@SuppressWarnings("unused") // Is injected
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
@InjectMocks
private FileObjectToFileObjectDtoMapperImpl mapper;
private final Subject subject = mock(Subject.class);
private final ThreadState subjectThreadState = new SubjectThreadState(subject);
private URI expectedBaseUri;
@Before
public void init() {
expectedBaseUri = baseUri.resolve(RepositoryRootResource.REPOSITORIES_PATH_V2 + "/");
subjectThreadState.bind();
ThreadContext.bind(subject);
}
@After
public void unbind() {
ThreadContext.unbindSubject();
}
@Test
public void shouldMapAttributesCorrectly() {
FileObject fileObject = createFileObject();
FileObjectDto dto = mapper.map(fileObject, new NamespaceAndName("namespace", "name"), "revision");
assertEqualAttributes(fileObject, dto);
}
@Test
public void shouldHaveCorrectSelfLinkForDirectory() {
FileObject fileObject = createDirectoryObject();
FileObjectDto dto = mapper.map(fileObject, new NamespaceAndName("namespace", "name"), "revision");
assertThat(dto.getLinks().getLinkBy("self").get().getHref()).isEqualTo(expectedBaseUri.resolve("namespace/name/sources/revision/foo/bar").toString());
}
@Test
public void shouldHaveCorrectContentLink() {
FileObject fileObject = createFileObject();
fileObject.setDirectory(false);
FileObjectDto dto = mapper.map(fileObject, new NamespaceAndName("namespace", "name"), "revision");
assertThat(dto.getLinks().getLinkBy("self").get().getHref()).isEqualTo(expectedBaseUri.resolve("namespace/name/content/revision/foo/bar").toString());
}
private FileObject createDirectoryObject() {
FileObject fileObject = createFileObject();
fileObject.setDirectory(true);
return fileObject;
}
private FileObject createFileObject() {
FileObject fileObject = new FileObject();
fileObject.setName("foo");
fileObject.setDescription("bar");
fileObject.setPath("foo/bar");
fileObject.setDirectory(false);
fileObject.setLength(100);
fileObject.setLastModified(123L);
fileObject.setSubRepository(new SubRepository("repo.url"));
return fileObject;
}
private void assertEqualAttributes(FileObject fileObject, FileObjectDto dto) {
assertThat(dto.getName()).isEqualTo(fileObject.getName());
assertThat(dto.getDescription()).isEqualTo(fileObject.getDescription());
assertThat(dto.getPath()).isEqualTo(fileObject.getPath());
assertThat(dto.isDirectory()).isEqualTo(fileObject.isDirectory());
assertThat(dto.getLength()).isEqualTo(fileObject.getLength());
assertThat(dto.getLastModified().toEpochMilli()).isEqualTo((long) fileObject.getLastModified());
assertThat(dto.getSubRepository().getBrowserUrl()).isEqualTo(fileObject.getSubRepository().getBrowserUrl());
}
}

View File

@@ -17,6 +17,7 @@ import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse;
import org.jboss.resteasy.spi.HttpRequest;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -141,6 +142,11 @@ public class PermissionRootResourceTest {
ThreadContext.bind(subject);
}
@After
public void unbind() {
ThreadContext.unbindSubject();
}
@TestFactory
@DisplayName("test endpoints on missing repository")
Stream<DynamicTest> missedRepositoryTestFactory() {

View File

@@ -135,8 +135,8 @@ public class ResourceLinksTest {
@Test
public void shouldCreateCorrectBranchSourceUrl() {
String url = resourceLinks.source().source("space", "name", "revision");
assertEquals(BASE_URL + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/name/sources/revision", url);
String url = resourceLinks.source().selfWithoutRevision("space", "name");
assertEquals(BASE_URL + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/name/sources/", url);
}
@Test
@@ -147,13 +147,18 @@ public class ResourceLinksTest {
@Test
public void shouldCreateCorrectSourceCollectionUrl() {
String url = resourceLinks.source().self("space", "repo");
String url = resourceLinks.source().selfWithoutRevision("space", "repo");
assertEquals(BASE_URL + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/sources/", url);
}
@Test
public void shouldCreateCorrectSourceUrlWithFilename() {
String url = resourceLinks.source().sourceWithPath("foo", "bar", "rev", "file");
assertEquals(BASE_URL + RepositoryRootResource.REPOSITORIES_PATH_V2 + "foo/bar/sources/rev/file", url);
}
@Test
public void shouldCreateCorrectPermissionCollectionUrl() {
String url = resourceLinks.source().self("space", "repo");
String url = resourceLinks.source().selfWithoutRevision("space", "repo");
assertEquals(BASE_URL + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/sources/", url);
}

View File

@@ -0,0 +1,160 @@
package sonia.scm.api.v2.resources;
import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.mock.MockDispatcherFactory;
import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.BrowserResult;
import sonia.scm.repository.FileObject;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.RepositoryNotFoundException;
import sonia.scm.repository.RevisionNotFoundException;
import sonia.scm.repository.api.BrowseCommandBuilder;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.Silent.class)
public class SourceRootResourceTest {
private final Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
private final URI baseUri = URI.create("/");
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
@Mock
private RepositoryServiceFactory serviceFactory;
@Mock
private RepositoryService service;
@Mock
private BrowseCommandBuilder browseCommandBuilder;
@Mock
private FileObjectToFileObjectDtoMapper fileObjectToFileObjectDtoMapper;
@InjectMocks
private BrowserResultToBrowserResultDtoMapper browserResultToBrowserResultDtoMapper;
@Before
public void prepareEnvironment() throws Exception {
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service);
when(service.getBrowseCommand()).thenReturn(browseCommandBuilder);
FileObjectDto dto = new FileObjectDto();
dto.setName("name");
dto.setLength(1024);
when(fileObjectToFileObjectDtoMapper.map(any(FileObject.class), any(NamespaceAndName.class), anyString())).thenReturn(dto);
SourceRootResource sourceRootResource = new SourceRootResource(serviceFactory, browserResultToBrowserResultDtoMapper);
RepositoryRootResource repositoryRootResource =
new RepositoryRootResource(MockProvider.of(new RepositoryResource(null,
null,
null,
null,
null,
null,
MockProvider.of(sourceRootResource),
null,
null)),
null);
dispatcher.getRegistry().addSingletonResource(repositoryRootResource);
}
@Test
public void shouldReturnSources() throws URISyntaxException, IOException, RevisionNotFoundException {
BrowserResult result = createBrowserResult();
when(browseCommandBuilder.getBrowserResult()).thenReturn(result);
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/sources");
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getContentAsString()).contains("\"revision\":\"revision\"");
assertThat(response.getContentAsString()).contains("\"tag\":\"tag\"");
assertThat(response.getContentAsString()).contains("\"branch\":\"branch\"");
assertThat(response.getContentAsString()).contains("\"files\":");
}
@Test
public void shouldReturn404IfRepoNotFound() throws URISyntaxException, RepositoryNotFoundException {
when(serviceFactory.create(new NamespaceAndName("idont", "exist"))).thenThrow(RepositoryNotFoundException.class);
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "idont/exist/sources");
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(404);
}
@Test
public void shouldGetResultForSingleFile() throws URISyntaxException, IOException, RevisionNotFoundException {
BrowserResult browserResult = new BrowserResult();
browserResult.setBranch("abc");
browserResult.setRevision("revision");
browserResult.setTag("tag");
FileObject fileObject = new FileObject();
fileObject.setName("File Object!");
browserResult.setFiles(Arrays.asList(fileObject));
when(browseCommandBuilder.getBrowserResult()).thenReturn(browserResult);
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/sources/revision/fileabc");
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getContentAsString()).contains("\"revision\":\"revision\"");
}
@Test
public void shouldGet404ForSingleFileIfRepoNotFound() throws URISyntaxException, RepositoryNotFoundException {
when(serviceFactory.create(new NamespaceAndName("idont", "exist"))).thenThrow(RepositoryNotFoundException.class);
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "idont/exist/sources/revision/fileabc");
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(404);
}
private BrowserResult createBrowserResult() {
return new BrowserResult("revision", "tag", "branch", createFileObjects());
}
private List<FileObject> createFileObjects() {
FileObject fileObject1 = new FileObject();
fileObject1.setName("FO 1");
fileObject1.setDirectory(false);
fileObject1.setDescription("File object 1");
fileObject1.setPath("/foo/bar/fo1");
fileObject1.setLength(1024L);
fileObject1.setLastModified(0L);
FileObject fileObject2 = new FileObject();
fileObject2.setName("FO 2");
fileObject2.setDirectory(true);
fileObject2.setDescription("File object 2");
fileObject2.setPath("/foo/bar/fo2");
fileObject2.setLength(4096L);
fileObject2.setLastModified(1234L);
return Arrays.asList(fileObject1, fileObject2);
}
}

View File

@@ -36,18 +36,26 @@ import com.github.sdorra.shiro.SubjectAware;
import com.google.common.collect.Sets;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.apache.shiro.util.ThreadContext;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;
import org.junit.Before;
import org.junit.Rule;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import static org.mockito.Mockito.*;
import org.mockito.junit.MockitoJUnitRunner;
import static org.hamcrest.Matchers.isEmptyOrNullString;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.when;
/**
* Unit test for {@link JwtAccessTokenBuilder}.
@@ -57,6 +65,10 @@ import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class JwtAccessTokenBuilderTest {
{
ThreadContext.unbindSubject();
}
@Mock
private KeyGenerator keyGenerator;