mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-15 09:46:16 +01:00
Add annotate rest resource
This commit is contained in:
@@ -71,13 +71,12 @@ public class VndMediaType {
|
||||
@SuppressWarnings("squid:S2068")
|
||||
public static final String PASSWORD_OVERWRITE = PREFIX + "passwordOverwrite" + SUFFIX;
|
||||
public static final String PERMISSION_COLLECTION = PREFIX + "permissionCollection" + SUFFIX;
|
||||
public static final String MERGE_RESULT = PREFIX + "mergeResult" + SUFFIX;
|
||||
public static final String MERGE_COMMAND = PREFIX + "mergeCommand" + SUFFIX;
|
||||
|
||||
public static final String NAMESPACE_STRATEGIES = PREFIX + "namespaceStrategies" + SUFFIX;
|
||||
|
||||
public static final String ME = PREFIX + "me" + SUFFIX;
|
||||
public static final String SOURCE = PREFIX + "source" + SUFFIX;
|
||||
public static final String ANNOTATE = PREFIX + "annotate" + SUFFIX;
|
||||
public static final String ERROR_TYPE = PREFIX + "error" + SUFFIX;
|
||||
|
||||
public static final String REPOSITORY_ROLE = PREFIX + "repositoryRole" + SUFFIX;
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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 io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
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.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import java.io.IOException;
|
||||
|
||||
public class AnnotateResource {
|
||||
|
||||
private final RepositoryServiceFactory serviceFactory;
|
||||
private final BlameResultToBlameDtoMapper mapper;
|
||||
|
||||
@Inject
|
||||
public AnnotateResource(RepositoryServiceFactory serviceFactory, BlameResultToBlameDtoMapper mapper) {
|
||||
this.serviceFactory = serviceFactory;
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content of a file with additional information for each line regarding the last commit that
|
||||
* changed this line: The revision, the author, the date, and the description of the commit.
|
||||
*
|
||||
* @param namespace the namespace of the repository
|
||||
* @param name the name of the repository
|
||||
* @param revision the revision
|
||||
* @param path The path of the file
|
||||
*/
|
||||
@GET
|
||||
@Path("{revision}/{path: .*}")
|
||||
@Produces(VndMediaType.ANNOTATE)
|
||||
@Operation(summary = "File content by revision", description = "Returns the annotated file for the given revision in the repository.", tags = "Repository")
|
||||
@ApiResponse(responseCode = "200", description = "success")
|
||||
@ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
|
||||
@ApiResponse(responseCode = "403", description = "not authorized, the current user has no privileges to read the repository")
|
||||
@ApiResponse(
|
||||
responseCode = "404",
|
||||
description = "not found, no repository with the specified name available in the namespace",
|
||||
content = @Content(
|
||||
mediaType = VndMediaType.ERROR_TYPE,
|
||||
schema = @Schema(implementation = ErrorDto.class)
|
||||
))
|
||||
@ApiResponse(
|
||||
responseCode = "500",
|
||||
description = "internal server error",
|
||||
content = @Content(
|
||||
mediaType = VndMediaType.ERROR_TYPE,
|
||||
schema = @Schema(implementation = ErrorDto.class)
|
||||
))
|
||||
public BlameDto annotate(
|
||||
@PathParam("namespace") String namespace,
|
||||
@PathParam("name") String name,
|
||||
@PathParam("revision") String revision,
|
||||
@PathParam("path") String path
|
||||
) throws IOException {
|
||||
NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, name);
|
||||
try (RepositoryService repositoryService = serviceFactory.create(namespaceAndName)) {
|
||||
return mapper.map(repositoryService.getBlameCommand().setRevision(revision).getBlameResult(path), namespaceAndName, revision, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,7 @@ abstract class BaseFileObjectDtoMapper extends HalAppenderMapper implements Inst
|
||||
} else {
|
||||
links.self(resourceLinks.source().content(namespaceAndName.getNamespace(), namespaceAndName.getName(), browserResult.getRevision(), path));
|
||||
links.single(link("history", resourceLinks.fileHistory().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), browserResult.getRevision(), path)));
|
||||
links.single(link("annotate", resourceLinks.annotate().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), browserResult.getRevision(), path)));
|
||||
}
|
||||
if (fileObject.isTruncated()) {
|
||||
links.single(link("proceed", resourceLinks.source().content(namespaceAndName.getNamespace(), namespaceAndName.getName(), browserResult.getRevision(), path) + "?offset=" + (offset + BrowseCommandRequest.DEFAULT_REQUEST_LIMIT)));
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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 de.otto.edison.hal.HalRepresentation;
|
||||
import de.otto.edison.hal.Links;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class BlameDto extends HalRepresentation {
|
||||
|
||||
private List<BlameLineDto> blameLines;
|
||||
|
||||
public BlameDto(Links links) {
|
||||
super(links);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 lombok.Data;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
@Data
|
||||
public class BlameLineDto {
|
||||
|
||||
private PersonDto author;
|
||||
private String code;
|
||||
private String description;
|
||||
private int lineNumber;
|
||||
private String revision;
|
||||
private Instant when;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.annotations.VisibleForTesting;
|
||||
import de.otto.edison.hal.Links;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.ObjectFactory;
|
||||
import sonia.scm.repository.BlameLine;
|
||||
import sonia.scm.repository.BlameResult;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.Person;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Mapper
|
||||
public abstract class BlameResultToBlameDtoMapper implements InstantAttributeMapper {
|
||||
|
||||
@Inject
|
||||
private ResourceLinks resourceLinks;
|
||||
|
||||
BlameDto map(BlameResult result, NamespaceAndName namespaceAndName, String revision, String path) {
|
||||
BlameDto dto = createDto(namespaceAndName, revision, path);
|
||||
dto.setBlameLines(result.getBlameLines().stream().map(this::map).collect(Collectors.toList()));
|
||||
return dto;
|
||||
}
|
||||
|
||||
abstract BlameLineDto map(BlameLine line);
|
||||
|
||||
abstract PersonDto map(Person person);
|
||||
|
||||
@ObjectFactory
|
||||
BlameDto createDto(NamespaceAndName namespaceAndName, String revision, String path) {
|
||||
return new BlameDto(Links.linkingTo()
|
||||
.self(
|
||||
resourceLinks.annotate()
|
||||
.self(
|
||||
namespaceAndName.getNamespace(),
|
||||
namespaceAndName.getName(),
|
||||
revision,
|
||||
path))
|
||||
.build());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setResourceLinks(ResourceLinks resourceLinks) {
|
||||
this.resourceLinks = resourceLinks;
|
||||
}
|
||||
}
|
||||
@@ -73,6 +73,8 @@ public class MapperModule extends AbstractModule {
|
||||
|
||||
bind(RepositoryToHalMapper.class).to(Mappers.getMapperClass(RepositoryToRepositoryDtoMapper.class));
|
||||
|
||||
bind(BlameResultToBlameDtoMapper.class).to(Mappers.getMapperClass(BlameResultToBlameDtoMapper.class));
|
||||
|
||||
// no mapstruct required
|
||||
bind(MeDtoFactory.class);
|
||||
bind(UIPluginDtoMapper.class);
|
||||
|
||||
@@ -38,6 +38,7 @@ public class RepositoryBasedResourceProvider {
|
||||
private final Provider<ModificationsRootResource> modificationsRootResource;
|
||||
private final Provider<FileHistoryRootResource> fileHistoryRootResource;
|
||||
private final Provider<IncomingRootResource> incomingRootResource;
|
||||
private final Provider<AnnotateResource> annotateResource;
|
||||
|
||||
@Inject
|
||||
public RepositoryBasedResourceProvider(
|
||||
@@ -50,7 +51,7 @@ public class RepositoryBasedResourceProvider {
|
||||
Provider<DiffRootResource> diffRootResource,
|
||||
Provider<ModificationsRootResource> modificationsRootResource,
|
||||
Provider<FileHistoryRootResource> fileHistoryRootResource,
|
||||
Provider<IncomingRootResource> incomingRootResource) {
|
||||
Provider<IncomingRootResource> incomingRootResource, Provider<AnnotateResource> annotateResource) {
|
||||
this.tagRootResource = tagRootResource;
|
||||
this.branchRootResource = branchRootResource;
|
||||
this.changesetRootResource = changesetRootResource;
|
||||
@@ -61,6 +62,7 @@ public class RepositoryBasedResourceProvider {
|
||||
this.modificationsRootResource = modificationsRootResource;
|
||||
this.fileHistoryRootResource = fileHistoryRootResource;
|
||||
this.incomingRootResource = incomingRootResource;
|
||||
this.annotateResource = annotateResource;
|
||||
}
|
||||
|
||||
public TagRootResource getTagRootResource() {
|
||||
@@ -102,4 +104,8 @@ public class RepositoryBasedResourceProvider {
|
||||
public IncomingRootResource getIncomingRootResource() {
|
||||
return incomingRootResource.get();
|
||||
}
|
||||
|
||||
public AnnotateResource getAnnotateResource() {
|
||||
return annotateResource.get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,6 +232,11 @@ public class RepositoryResource {
|
||||
return resourceProvider.getIncomingRootResource();
|
||||
}
|
||||
|
||||
@Path("annotate/")
|
||||
public AnnotateResource annotate() {
|
||||
return resourceProvider.getAnnotateResource();
|
||||
}
|
||||
|
||||
private Supplier<Repository> loadBy(String namespace, String name) {
|
||||
NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, name);
|
||||
return () -> Optional.ofNullable(manager.get(namespaceAndName)).orElseThrow(() -> notFound(entity(namespaceAndName)));
|
||||
|
||||
@@ -566,6 +566,22 @@ class ResourceLinks {
|
||||
}
|
||||
}
|
||||
|
||||
public AnnotateLinks annotate() {
|
||||
return new AnnotateLinks(scmPathInfoStore.get());
|
||||
}
|
||||
|
||||
static class AnnotateLinks {
|
||||
private final LinkBuilder annotateLinkBuilder;
|
||||
|
||||
AnnotateLinks(ScmPathInfo pathInfo) {
|
||||
this.annotateLinkBuilder = new LinkBuilder(pathInfo, RepositoryRootResource.class, RepositoryResource.class, AnnotateResource.class);
|
||||
}
|
||||
|
||||
String self(String namespace, String name, String revision, String path) {
|
||||
return annotateLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("annotate").parameters().method("annotate").parameters(revision, path).href();
|
||||
}
|
||||
}
|
||||
|
||||
RepositoryVerbLinks repositoryVerbs() {
|
||||
return new RepositoryVerbLinks(scmPathInfoStore.get());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* 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.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.jboss.resteasy.mock.MockHttpRequest;
|
||||
import org.jboss.resteasy.mock.MockHttpResponse;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.repository.BlameLine;
|
||||
import sonia.scm.repository.BlameResult;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.Person;
|
||||
import sonia.scm.repository.api.BlameCommandBuilder;
|
||||
import sonia.scm.repository.api.RepositoryService;
|
||||
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||
import sonia.scm.web.RestDispatcher;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Iterator;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class AnnotateResourceTest extends RepositoryTestBase {
|
||||
|
||||
public static final NamespaceAndName NAMESPACE_AND_NAME = new NamespaceAndName("space", "X");
|
||||
public static final String REVISION = "123";
|
||||
public static final String PATH = "some/file";
|
||||
@Mock
|
||||
private RepositoryServiceFactory serviceFactory;
|
||||
@Mock
|
||||
private RepositoryService service;
|
||||
@Mock
|
||||
private BlameCommandBuilder blameCommandBuilder;
|
||||
|
||||
private final RestDispatcher dispatcher = new RestDispatcher();
|
||||
private final MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
@BeforeEach
|
||||
void initResource() {
|
||||
BlameResultToBlameDtoMapperImpl mapper = new BlameResultToBlameDtoMapperImpl();
|
||||
mapper.setResourceLinks(ResourceLinksMock.createMock(URI.create("/")));
|
||||
annotateResource = new AnnotateResource(serviceFactory, mapper);
|
||||
dispatcher.addSingletonResource(getRepositoryRootResource());
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void initRepositoryService() {
|
||||
when(serviceFactory.create(NAMESPACE_AND_NAME)).thenReturn(service);
|
||||
when(service.getBlameCommand()).thenReturn(blameCommandBuilder);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void initBlameCommand() throws IOException {
|
||||
BlameLine line1 = new BlameLine(
|
||||
0,
|
||||
"100",
|
||||
System.currentTimeMillis(),
|
||||
new Person("Arthur Dent", "arthur@hitchhiker.com"),
|
||||
"first try",
|
||||
"jump"
|
||||
);
|
||||
BlameLine line2 = new BlameLine(
|
||||
1,
|
||||
"42",
|
||||
System.currentTimeMillis(),
|
||||
new Person("Zaphod Beeblebrox", "zaphod@hitchhiker.com"),
|
||||
"got it",
|
||||
"heart of gold"
|
||||
);
|
||||
BlameResult result = new BlameResult(asList(line1, line2));
|
||||
when(blameCommandBuilder.setRevision(REVISION)).thenReturn(blameCommandBuilder);
|
||||
when(blameCommandBuilder.getBlameResult(PATH)).thenReturn(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test() throws URISyntaxException, UnsupportedEncodingException, JsonProcessingException {
|
||||
MockHttpRequest request = MockHttpRequest
|
||||
.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + NAMESPACE_AND_NAME + "/annotate/" + REVISION + "/" + PATH);
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
|
||||
String content = response.getContentAsString();
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
JsonNode jsonNode = mapper.readTree(content);
|
||||
JsonNode blameLines = jsonNode.get("blameLines");
|
||||
assertThat(blameLines.isArray()).isTrue();
|
||||
assertThat(jsonNode.get("_links").get("self").get("href").asText())
|
||||
.isEqualTo("/v2/repositories/space/X/annotate/123/some%2Ffile");
|
||||
|
||||
Iterator<JsonNode> lineIterator = blameLines.iterator();
|
||||
JsonNode line1 = lineIterator.next();
|
||||
assertThat(line1.get("author").get("mail").asText()).isEqualTo("arthur@hitchhiker.com");
|
||||
assertThat(line1.get("author").get("name").asText()).isEqualTo("Arthur Dent");
|
||||
assertThat(line1.get("code").asText()).isEqualTo("jump");
|
||||
assertThat(line1.get("description").asText()).isEqualTo("first try");
|
||||
assertThat(line1.get("lineNumber").asInt()).isEqualTo(0);
|
||||
assertThat(line1.get("revision").asText()).isEqualTo("100");
|
||||
|
||||
JsonNode line2 = lineIterator.next();
|
||||
assertThat(line2.get("author").get("mail").asText()).isEqualTo("zaphod@hitchhiker.com");
|
||||
assertThat(line2.get("author").get("name").asText()).isEqualTo("Zaphod Beeblebrox");
|
||||
assertThat(line2.get("code").asText()).isEqualTo("heart of gold");
|
||||
assertThat(line2.get("description").asText()).isEqualTo("got it");
|
||||
assertThat(line2.get("lineNumber").asInt()).isEqualTo(1);
|
||||
assertThat(line2.get("revision").asText()).isEqualTo("42");
|
||||
|
||||
assertThat(lineIterator.hasNext()).isFalse();
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,7 @@ abstract class RepositoryTestBase {
|
||||
FileHistoryRootResource fileHistoryRootResource;
|
||||
IncomingRootResource incomingRootResource;
|
||||
RepositoryCollectionResource repositoryCollectionResource;
|
||||
AnnotateResource annotateResource;
|
||||
|
||||
|
||||
RepositoryRootResource getRepositoryRootResource() {
|
||||
@@ -58,8 +59,8 @@ abstract class RepositoryTestBase {
|
||||
of(diffRootResource),
|
||||
of(modificationsRootResource),
|
||||
of(fileHistoryRootResource),
|
||||
of(incomingRootResource)
|
||||
);
|
||||
of(incomingRootResource),
|
||||
of(annotateResource));
|
||||
return new RepositoryRootResource(
|
||||
of(new RepositoryResource(
|
||||
repositoryToDtoMapper,
|
||||
|
||||
@@ -26,6 +26,7 @@ package sonia.scm.api.v2.resources;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -33,47 +34,48 @@ public class ResourceLinksMock {
|
||||
public static ResourceLinks createMock(URI baseUri) {
|
||||
ResourceLinks resourceLinks = mock(ResourceLinks.class);
|
||||
|
||||
ScmPathInfo uriInfo = mock(ScmPathInfo.class);
|
||||
when(uriInfo.getApiRestUri()).thenReturn(baseUri);
|
||||
ScmPathInfo pathInfo = mock(ScmPathInfo.class);
|
||||
when(pathInfo.getApiRestUri()).thenReturn(baseUri);
|
||||
|
||||
ResourceLinks.UserLinks userLinks = new ResourceLinks.UserLinks(uriInfo);
|
||||
when(resourceLinks.user()).thenReturn(userLinks);
|
||||
when(resourceLinks.me()).thenReturn(new ResourceLinks.MeLinks(uriInfo,userLinks));
|
||||
when(resourceLinks.userCollection()).thenReturn(new ResourceLinks.UserCollectionLinks(uriInfo));
|
||||
when(resourceLinks.userPermissions()).thenReturn(new ResourceLinks.UserPermissionLinks(uriInfo));
|
||||
when(resourceLinks.autoComplete()).thenReturn(new ResourceLinks.AutoCompleteLinks(uriInfo));
|
||||
when(resourceLinks.group()).thenReturn(new ResourceLinks.GroupLinks(uriInfo));
|
||||
when(resourceLinks.groupCollection()).thenReturn(new ResourceLinks.GroupCollectionLinks(uriInfo));
|
||||
when(resourceLinks.groupPermissions()).thenReturn(new ResourceLinks.GroupPermissionLinks(uriInfo));
|
||||
when(resourceLinks.repository()).thenReturn(new ResourceLinks.RepositoryLinks(uriInfo));
|
||||
when(resourceLinks.incoming()).thenReturn(new ResourceLinks.IncomingLinks(uriInfo));
|
||||
when(resourceLinks.repositoryCollection()).thenReturn(new ResourceLinks.RepositoryCollectionLinks(uriInfo));
|
||||
when(resourceLinks.tag()).thenReturn(new ResourceLinks.TagCollectionLinks(uriInfo));
|
||||
when(resourceLinks.branchCollection()).thenReturn(new ResourceLinks.BranchCollectionLinks(uriInfo));
|
||||
when(resourceLinks.changeset()).thenReturn(new ResourceLinks.ChangesetLinks(uriInfo));
|
||||
when(resourceLinks.fileHistory()).thenReturn(new ResourceLinks.FileHistoryLinks(uriInfo));
|
||||
when(resourceLinks.source()).thenReturn(new ResourceLinks.SourceLinks(uriInfo));
|
||||
when(resourceLinks.repositoryPermission()).thenReturn(new ResourceLinks.RepositoryPermissionLinks(uriInfo));
|
||||
when(resourceLinks.config()).thenReturn(new ResourceLinks.ConfigLinks(uriInfo));
|
||||
when(resourceLinks.branch()).thenReturn(new ResourceLinks.BranchLinks(uriInfo));
|
||||
when(resourceLinks.diff()).thenReturn(new ResourceLinks.DiffLinks(uriInfo));
|
||||
when(resourceLinks.modifications()).thenReturn(new ResourceLinks.ModificationsLinks(uriInfo));
|
||||
when(resourceLinks.repositoryType()).thenReturn(new ResourceLinks.RepositoryTypeLinks(uriInfo));
|
||||
when(resourceLinks.repositoryTypeCollection()).thenReturn(new ResourceLinks.RepositoryTypeCollectionLinks(uriInfo));
|
||||
when(resourceLinks.installedPluginCollection()).thenReturn(new ResourceLinks.InstalledPluginCollectionLinks(uriInfo));
|
||||
when(resourceLinks.availablePluginCollection()).thenReturn(new ResourceLinks.AvailablePluginCollectionLinks(uriInfo));
|
||||
when(resourceLinks.pendingPluginCollection()).thenReturn(new ResourceLinks.PendingPluginCollectionLinks(uriInfo));
|
||||
when(resourceLinks.installedPlugin()).thenReturn(new ResourceLinks.InstalledPluginLinks(uriInfo));
|
||||
when(resourceLinks.availablePlugin()).thenReturn(new ResourceLinks.AvailablePluginLinks(uriInfo));
|
||||
when(resourceLinks.uiPluginCollection()).thenReturn(new ResourceLinks.UIPluginCollectionLinks(uriInfo));
|
||||
when(resourceLinks.uiPlugin()).thenReturn(new ResourceLinks.UIPluginLinks(uriInfo));
|
||||
when(resourceLinks.authentication()).thenReturn(new ResourceLinks.AuthenticationLinks(uriInfo));
|
||||
when(resourceLinks.index()).thenReturn(new ResourceLinks.IndexLinks(uriInfo));
|
||||
when(resourceLinks.permissions()).thenReturn(new ResourceLinks.PermissionsLinks(uriInfo));
|
||||
when(resourceLinks.repositoryVerbs()).thenReturn(new ResourceLinks.RepositoryVerbLinks(uriInfo));
|
||||
when(resourceLinks.repositoryRole()).thenReturn(new ResourceLinks.RepositoryRoleLinks(uriInfo));
|
||||
when(resourceLinks.repositoryRoleCollection()).thenReturn(new ResourceLinks.RepositoryRoleCollectionLinks(uriInfo));
|
||||
when(resourceLinks.namespaceStrategies()).thenReturn(new ResourceLinks.NamespaceStrategiesLinks(uriInfo));
|
||||
ResourceLinks.UserLinks userLinks = new ResourceLinks.UserLinks(pathInfo);
|
||||
lenient().when(resourceLinks.user()).thenReturn(userLinks);
|
||||
lenient().when(resourceLinks.me()).thenReturn(new ResourceLinks.MeLinks(pathInfo,userLinks));
|
||||
lenient().when(resourceLinks.userCollection()).thenReturn(new ResourceLinks.UserCollectionLinks(pathInfo));
|
||||
lenient().when(resourceLinks.userPermissions()).thenReturn(new ResourceLinks.UserPermissionLinks(pathInfo));
|
||||
lenient().when(resourceLinks.autoComplete()).thenReturn(new ResourceLinks.AutoCompleteLinks(pathInfo));
|
||||
lenient().when(resourceLinks.group()).thenReturn(new ResourceLinks.GroupLinks(pathInfo));
|
||||
lenient().when(resourceLinks.groupCollection()).thenReturn(new ResourceLinks.GroupCollectionLinks(pathInfo));
|
||||
lenient().when(resourceLinks.groupPermissions()).thenReturn(new ResourceLinks.GroupPermissionLinks(pathInfo));
|
||||
lenient().when(resourceLinks.repository()).thenReturn(new ResourceLinks.RepositoryLinks(pathInfo));
|
||||
lenient().when(resourceLinks.incoming()).thenReturn(new ResourceLinks.IncomingLinks(pathInfo));
|
||||
lenient().when(resourceLinks.repositoryCollection()).thenReturn(new ResourceLinks.RepositoryCollectionLinks(pathInfo));
|
||||
lenient().when(resourceLinks.tag()).thenReturn(new ResourceLinks.TagCollectionLinks(pathInfo));
|
||||
lenient().when(resourceLinks.branchCollection()).thenReturn(new ResourceLinks.BranchCollectionLinks(pathInfo));
|
||||
lenient().when(resourceLinks.changeset()).thenReturn(new ResourceLinks.ChangesetLinks(pathInfo));
|
||||
lenient().when(resourceLinks.fileHistory()).thenReturn(new ResourceLinks.FileHistoryLinks(pathInfo));
|
||||
lenient().when(resourceLinks.source()).thenReturn(new ResourceLinks.SourceLinks(pathInfo));
|
||||
lenient().when(resourceLinks.repositoryPermission()).thenReturn(new ResourceLinks.RepositoryPermissionLinks(pathInfo));
|
||||
lenient().when(resourceLinks.config()).thenReturn(new ResourceLinks.ConfigLinks(pathInfo));
|
||||
lenient().when(resourceLinks.branch()).thenReturn(new ResourceLinks.BranchLinks(pathInfo));
|
||||
lenient().when(resourceLinks.diff()).thenReturn(new ResourceLinks.DiffLinks(pathInfo));
|
||||
lenient().when(resourceLinks.modifications()).thenReturn(new ResourceLinks.ModificationsLinks(pathInfo));
|
||||
lenient().when(resourceLinks.repositoryType()).thenReturn(new ResourceLinks.RepositoryTypeLinks(pathInfo));
|
||||
lenient().when(resourceLinks.repositoryTypeCollection()).thenReturn(new ResourceLinks.RepositoryTypeCollectionLinks(pathInfo));
|
||||
lenient().when(resourceLinks.installedPluginCollection()).thenReturn(new ResourceLinks.InstalledPluginCollectionLinks(pathInfo));
|
||||
lenient().when(resourceLinks.availablePluginCollection()).thenReturn(new ResourceLinks.AvailablePluginCollectionLinks(pathInfo));
|
||||
lenient().when(resourceLinks.pendingPluginCollection()).thenReturn(new ResourceLinks.PendingPluginCollectionLinks(pathInfo));
|
||||
lenient().when(resourceLinks.installedPlugin()).thenReturn(new ResourceLinks.InstalledPluginLinks(pathInfo));
|
||||
lenient().when(resourceLinks.availablePlugin()).thenReturn(new ResourceLinks.AvailablePluginLinks(pathInfo));
|
||||
lenient().when(resourceLinks.uiPluginCollection()).thenReturn(new ResourceLinks.UIPluginCollectionLinks(pathInfo));
|
||||
lenient().when(resourceLinks.uiPlugin()).thenReturn(new ResourceLinks.UIPluginLinks(pathInfo));
|
||||
lenient().when(resourceLinks.authentication()).thenReturn(new ResourceLinks.AuthenticationLinks(pathInfo));
|
||||
lenient().when(resourceLinks.index()).thenReturn(new ResourceLinks.IndexLinks(pathInfo));
|
||||
lenient().when(resourceLinks.permissions()).thenReturn(new ResourceLinks.PermissionsLinks(pathInfo));
|
||||
lenient().when(resourceLinks.repositoryVerbs()).thenReturn(new ResourceLinks.RepositoryVerbLinks(pathInfo));
|
||||
lenient().when(resourceLinks.repositoryRole()).thenReturn(new ResourceLinks.RepositoryRoleLinks(pathInfo));
|
||||
lenient().when(resourceLinks.repositoryRoleCollection()).thenReturn(new ResourceLinks.RepositoryRoleCollectionLinks(pathInfo));
|
||||
lenient().when(resourceLinks.namespaceStrategies()).thenReturn(new ResourceLinks.NamespaceStrategiesLinks(pathInfo));
|
||||
lenient().when(resourceLinks.annotate()).thenReturn(new ResourceLinks.AnnotateLinks(pathInfo));
|
||||
|
||||
return resourceLinks;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user