diff --git a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java index 20735f83fb..ddcce9f4e8 100644 --- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java +++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java @@ -21,6 +21,8 @@ public class VndMediaType { public static final String PERMISSION = PREFIX + "permission" + SUFFIX; public static final String CHANGESET = PREFIX + "changeset" + SUFFIX; public static final String CHANGESET_COLLECTION = PREFIX + "changesetCollection" + SUFFIX; + public static final String TAG = PREFIX + "tag" + SUFFIX; + public static final String TAG_COLLECTION = PREFIX + "tagCollection" + SUFFIX; public static final String BRANCH = PREFIX + "branch" + SUFFIX; public static final String DIFF = PLAIN_TEXT_PREFIX + "diff" + PLAIN_TEXT_SUFFIX; public static final String USER_COLLECTION = PREFIX + "userCollection" + SUFFIX; diff --git a/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java b/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java index 1722c7e4fa..399dd26035 100644 --- a/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java +++ b/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java @@ -1,5 +1,7 @@ package sonia.scm.it; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; import org.apache.http.HttpStatus; import org.junit.Assume; import org.junit.Before; @@ -8,8 +10,10 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import sonia.scm.repository.Changeset; import sonia.scm.repository.client.api.ClientCommand; import sonia.scm.repository.client.api.RepositoryClient; +import sonia.scm.web.VndMediaType; import java.io.File; import java.io.IOException; @@ -20,6 +24,8 @@ import static java.lang.Thread.sleep; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertNotNull; +import static sonia.scm.it.RestUtil.ADMIN_PASSWORD; +import static sonia.scm.it.RestUtil.ADMIN_USERNAME; import static sonia.scm.it.RestUtil.given; import static sonia.scm.it.ScmTypes.availableScmTypes; @@ -74,6 +80,85 @@ public class RepositoryAccessITCase { assertNotNull(branchName); } + @Test + public void shouldFindTags() throws IOException { + RepositoryClient repositoryClient = RepositoryUtil.createRepositoryClient(repositoryType, folder); + + Assume.assumeTrue("There are no tags for " + repositoryType, repositoryClient.isCommandSupported(ClientCommand.TAG)); + + Changeset changeset = RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, "a.txt", "a"); + + String tagName = "v1.0"; + String repositoryUrl = TestData.getDefaultRepositoryUrl(repositoryType); + String tagsUrl = given() + .when() + .get(repositoryUrl) + .then() + .statusCode(HttpStatus.SC_OK) + .extract() + .path("_links.tags.href"); + + ExtractableResponse response = given(VndMediaType.TAG_COLLECTION, ADMIN_USERNAME, ADMIN_PASSWORD) + .when() + .get(tagsUrl) + .then() + .statusCode(HttpStatus.SC_OK) + .extract(); + + assertThat(response).isNotNull(); + assertThat(response.body()).isNotNull(); + assertThat(response.body().asString()) + .isNotNull() + .isNotBlank(); + + RepositoryUtil.addTag(repositoryClient, changeset.getId(), tagName); + response = given(VndMediaType.TAG_COLLECTION, ADMIN_USERNAME, ADMIN_PASSWORD) + .when() + .get(tagsUrl) + .then() + .statusCode(HttpStatus.SC_OK) + .extract(); + + assertThat(response).isNotNull(); + assertThat(response.body()).isNotNull(); + assertThat(response.body().asString()) + .isNotNull() + .isNotBlank(); + + assertThat(response.body().jsonPath().getString("_links.self.href")) + .as("assert tags self link") + .isNotNull() + .contains(repositoryUrl + "/tags/"); + + assertThat(response.body().jsonPath().getList("_embedded.tags")) + .as("assert tag size") + .isNotNull() + .size() + .isGreaterThan(0); + + assertThat(response.body().jsonPath().getMap("_embedded.tags.find{it.name=='" + tagName + "'}")) + .as("assert tag name and revision") + .isNotNull() + .hasSize(3) + .containsEntry("name", tagName) + .containsEntry("revision", changeset.getId()); + + assertThat(response.body().jsonPath().getString("_embedded.tags.find{it.name=='" + tagName + "'}._links.self.href")) + .as("assert single tag self link") + .isNotNull() + .contains(String.format("%s/tags/%s", repositoryUrl, tagName)); + + assertThat(response.body().jsonPath().getString("_embedded.tags.find{it.name=='" + tagName + "'}._links.sources.href")) + .as("assert single tag source link") + .isNotNull() + .contains(String.format("%s/sources/%s", repositoryUrl, changeset.getId())); + + assertThat(response.body().jsonPath().getString("_embedded.tags.find{it.name=='" + tagName + "'}._links.changesets.href")) + .as("assert single tag changesets link") + .isNotNull() + .contains(String.format("%s/changesets/%s", repositoryUrl, changeset.getId())); + } + @Test public void shouldReadContent() throws IOException, InterruptedException { RepositoryClient repositoryClient = RepositoryUtil.createRepositoryClient(repositoryType, folder); @@ -97,7 +182,8 @@ public class RepositoryAccessITCase { .then() .statusCode(HttpStatus.SC_OK) .extract() - .path("files.find{it.name=='a.txt'}._links.self.href"); + .path("_embedded.files.find{it.name=='a.txt'}._links.self.href"); + given() .when() .get(rootContentUrl) @@ -111,14 +197,22 @@ public class RepositoryAccessITCase { .then() .statusCode(HttpStatus.SC_OK) .extract() - .path("files.find{it.name=='subfolder'}._links.self.href"); - String subfolderContentUrl= given() + .path("_embedded.files.find{it.name=='subfolder'}._links.self.href"); + String selfOfSubfolderUrl = given() .when() .get(subfolderSourceUrl) .then() .statusCode(HttpStatus.SC_OK) .extract() - .path("files[0]._links.self.href"); + .path("_links.self.href"); + assertThat(subfolderSourceUrl).isEqualTo(selfOfSubfolderUrl); + String subfolderContentUrl = given() + .when() + .get(subfolderSourceUrl) + .then() + .statusCode(HttpStatus.SC_OK) + .extract() + .path("_embedded.files[0]._links.self.href"); given() .when() .get(subfolderContentUrl) diff --git a/scm-it/src/test/java/sonia/scm/it/RepositoryUtil.java b/scm-it/src/test/java/sonia/scm/it/RepositoryUtil.java index e49927b1b9..442856eefa 100644 --- a/scm-it/src/test/java/sonia/scm/it/RepositoryUtil.java +++ b/scm-it/src/test/java/sonia/scm/it/RepositoryUtil.java @@ -7,6 +7,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.repository.Changeset; import sonia.scm.repository.Person; +import sonia.scm.repository.Tag; import sonia.scm.repository.client.api.ClientCommand; import sonia.scm.repository.client.api.RepositoryClient; import sonia.scm.repository.client.api.RepositoryClientFactory; @@ -40,11 +41,11 @@ public class RepositoryUtil { return name; } - static void createAndCommitFile(RepositoryClient repositoryClient, String username, String fileName, String content) throws IOException { + static Changeset createAndCommitFile(RepositoryClient repositoryClient, String username, String fileName, String content) throws IOException { File file = new File(repositoryClient.getWorkingCopy(), fileName); Files.write(content, file, Charsets.UTF_8); addWithParentDirectories(repositoryClient, file); - commit(repositoryClient, username, "added " + fileName); + return commit(repositoryClient, username, "added " + fileName); } private static String addWithParentDirectories(RepositoryClient repositoryClient, File file) throws IOException { @@ -69,4 +70,16 @@ public class RepositoryUtil { } return changeset; } + + static Tag addTag(RepositoryClient repositoryClient, String revision, String tagName) throws IOException { + if (repositoryClient.isCommandSupported(ClientCommand.TAG)) { + Tag tag = repositoryClient.getTagCommand().setRevision(revision).tag(tagName, TestData.USER_SCM_ADMIN); + if (repositoryClient.isCommandSupported(ClientCommand.PUSH)) { + repositoryClient.getPushCommand().pushTags(); + } + return tag; + } + + return null; + } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitPushCommand.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitPushCommand.java index 3b9d29abdf..8d65b54b06 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitPushCommand.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitPushCommand.java @@ -37,12 +37,12 @@ package sonia.scm.repository.client.spi; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.transport.CredentialsProvider; - import sonia.scm.repository.client.api.RepositoryClientException; -//~--- JDK imports ------------------------------------------------------------ - import java.io.IOException; +import java.util.function.Supplier; + +//~--- JDK imports ------------------------------------------------------------ /** * @@ -73,11 +73,20 @@ public class GitPushCommand implements PushCommand * @throws IOException */ @Override - public void push() throws IOException + public void push() throws IOException { + push(() -> git.push().setPushAll()); + } + + @Override + public void pushTags() throws IOException { + push(() -> git.push().setPushTags()); + } + + private void push(Supplier commandSupplier) throws RepositoryClientException { try { - org.eclipse.jgit.api.PushCommand cmd = git.push().setPushAll(); + org.eclipse.jgit.api.PushCommand cmd = commandSupplier.get(); if (credentialsProvider != null) { diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitTagCommand.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitTagCommand.java index ee03dc7458..e5e1f36155 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitTagCommand.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitTagCommand.java @@ -35,22 +35,20 @@ package sonia.scm.repository.client.spi; //~--- non-JDK imports -------------------------------------------------------- import com.google.common.base.Strings; - import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; - import sonia.scm.repository.GitUtil; import sonia.scm.repository.Tag; import sonia.scm.repository.client.api.RepositoryClientException; -//~--- JDK imports ------------------------------------------------------------ - import java.io.IOException; +//~--- JDK imports ------------------------------------------------------------ + /** * * @author Sebastian Sdorra @@ -119,7 +117,11 @@ public class GitTagCommand implements TagCommand ref = git.tag().setObjectId(revObject).call(); } - tag = new Tag(request.getName(), ref.getPeeledObjectId().toString()); + if (ref.isPeeled()) { + tag = new Tag(request.getName(), ref.getPeeledObjectId().toString()); + } else { + tag = new Tag(request.getName(), ref.getObjectId().toString()); + } } catch (GitAPIException ex) diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/client/spi/HgPushCommand.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/client/spi/HgPushCommand.java index 73e0a1f9a4..263347ca00 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/client/spi/HgPushCommand.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/client/spi/HgPushCommand.java @@ -32,9 +32,10 @@ package sonia.scm.repository.client.spi; import com.aragost.javahg.Repository; import com.aragost.javahg.commands.ExecutionException; -import java.io.IOException; import sonia.scm.repository.client.api.RepositoryClientException; +import java.io.IOException; + /** * Mercurial implementation of the {@link PushCommand}. * @@ -63,5 +64,10 @@ public class HgPushCommand implements PushCommand throw new RepositoryClientException("push to repository failed", ex); } } - + + @Override + public void pushTags() throws IOException { + push(); + } + } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/client/spi/HgTagCommand.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/client/spi/HgTagCommand.java index 3aa448bfca..639adf9a1b 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/client/spi/HgTagCommand.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/client/spi/HgTagCommand.java @@ -32,7 +32,6 @@ package sonia.scm.repository.client.spi; import com.aragost.javahg.Repository; import com.google.common.base.Strings; -import java.io.IOException; import sonia.scm.repository.Tag; /** @@ -51,13 +50,16 @@ public class HgTagCommand implements TagCommand } @Override - public Tag tag(TagRequest request) throws IOException + public Tag tag(TagRequest request) { String rev = request.getRevision(); if ( Strings.isNullOrEmpty(rev) ){ rev = repository.tip().getNode(); } - com.aragost.javahg.commands.TagCommand.on(repository).rev(rev).execute(request.getName()); + com.aragost.javahg.commands.TagCommand.on(repository) + .rev(rev) + .user(request.getUserName()) + .execute(request.getName()); return new Tag(request.getName(), rev); } diff --git a/scm-test/src/main/java/sonia/scm/repository/client/api/PushCommandBuilder.java b/scm-test/src/main/java/sonia/scm/repository/client/api/PushCommandBuilder.java index b3b7619e7b..06b59e4ba0 100644 --- a/scm-test/src/main/java/sonia/scm/repository/client/api/PushCommandBuilder.java +++ b/scm-test/src/main/java/sonia/scm/repository/client/api/PushCommandBuilder.java @@ -36,13 +36,12 @@ package sonia.scm.repository.client.api; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import sonia.scm.repository.client.spi.PushCommand; -//~--- JDK imports ------------------------------------------------------------ - import java.io.IOException; +//~--- JDK imports ------------------------------------------------------------ + /** * * @author Sebastian Sdorra @@ -88,6 +87,14 @@ public final class PushCommandBuilder command.push(); } + public void pushTags() throws IOException { + if (logger.isDebugEnabled()) { + logger.debug("push tag changes back to main repository"); + } + + command.pushTags(); + } + //~--- fields --------------------------------------------------------------- /** Field description */ diff --git a/scm-test/src/main/java/sonia/scm/repository/client/api/TagCommandBuilder.java b/scm-test/src/main/java/sonia/scm/repository/client/api/TagCommandBuilder.java index 8b42817aa8..1d153dee0e 100644 --- a/scm-test/src/main/java/sonia/scm/repository/client/api/TagCommandBuilder.java +++ b/scm-test/src/main/java/sonia/scm/repository/client/api/TagCommandBuilder.java @@ -36,15 +36,14 @@ package sonia.scm.repository.client.api; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import sonia.scm.repository.Tag; import sonia.scm.repository.client.spi.TagCommand; import sonia.scm.repository.client.spi.TagRequest; -//~--- JDK imports ------------------------------------------------------------ - import java.io.IOException; +//~--- JDK imports ------------------------------------------------------------ + /** * * @author Sebastian Sdorra @@ -84,9 +83,10 @@ public final class TagCommandBuilder * * @throws IOException */ - public Tag tag(String name) throws IOException + public Tag tag(String name, String username) throws IOException { request.setName(name); + request.setUsername(username); if (logger.isDebugEnabled()) { diff --git a/scm-test/src/main/java/sonia/scm/repository/client/spi/PushCommand.java b/scm-test/src/main/java/sonia/scm/repository/client/spi/PushCommand.java index 5e85ed0e5e..be8fd94718 100644 --- a/scm-test/src/main/java/sonia/scm/repository/client/spi/PushCommand.java +++ b/scm-test/src/main/java/sonia/scm/repository/client/spi/PushCommand.java @@ -44,11 +44,7 @@ import java.io.IOException; public interface PushCommand { - /** - * Method description - * - * - * @throws IOException - */ - public void push() throws IOException; + void push() throws IOException; + + void pushTags() throws IOException; } diff --git a/scm-test/src/main/java/sonia/scm/repository/client/spi/TagRequest.java b/scm-test/src/main/java/sonia/scm/repository/client/spi/TagRequest.java index ff34309b6d..53abf0f8f8 100644 --- a/scm-test/src/main/java/sonia/scm/repository/client/spi/TagRequest.java +++ b/scm-test/src/main/java/sonia/scm/repository/client/spi/TagRequest.java @@ -91,6 +91,7 @@ public final class TagRequest { this.name = null; this.revision = null; + this.username = null; } /** @@ -106,6 +107,7 @@ public final class TagRequest return MoreObjects.toStringHelper(this) .add("revision", revision) .add("name", name) + .add("username", username) .toString(); //J+ } @@ -134,6 +136,10 @@ public final class TagRequest this.revision = revision; } + public void setUsername(String username) { + this.username = username; + } + //~--- get methods ---------------------------------------------------------- /** @@ -158,6 +164,10 @@ public final class TagRequest return revision; } + public String getUserName() { + return username; + } + //~--- fields --------------------------------------------------------------- /** Field description */ @@ -165,4 +175,6 @@ public final class TagRequest /** Field description */ private String revision; + + private String username; } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultDto.java index b8ffd7ff26..2b49f18fa1 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultDto.java @@ -6,18 +6,13 @@ 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 { +public class BrowserResultDto extends HalRepresentation { private String revision; - private String tag; - private String branch; - // REVIEW files nicht embedded? - private List files; @Override @SuppressWarnings("squid:S1185") // We want to have this method available in this package @@ -25,16 +20,7 @@ public class BrowserResultDto extends HalRepresentation implements Iterable iterator() { - Iterator it = null; - - if (files != null) - { - it = files.iterator(); - } - - return it; + public void setFiles(List files) { + this.withEmbedded("files", files); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToBrowserResultDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToBrowserResultDtoMapper.java index 7abb1ae69b..c877cb0647 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToBrowserResultDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToBrowserResultDtoMapper.java @@ -17,11 +17,9 @@ public class BrowserResultToBrowserResultDtoMapper { @Inject private ResourceLinks resourceLinks; - public BrowserResultDto map(BrowserResult browserResult, NamespaceAndName namespaceAndName) { + public BrowserResultDto map(BrowserResult browserResult, NamespaceAndName namespaceAndName, String path) { BrowserResultDto browserResultDto = new BrowserResultDto(); - browserResultDto.setTag(browserResult.getTag()); - browserResultDto.setBranch(browserResult.getBranch()); browserResultDto.setRevision(browserResult.getRevision()); List fileObjectDtoList = new ArrayList<>(); @@ -30,7 +28,7 @@ public class BrowserResultToBrowserResultDtoMapper { } browserResultDto.setFiles(fileObjectDtoList); - this.addLinks(browserResult, browserResultDto, namespaceAndName); + this.addLinks(browserResult, browserResultDto, namespaceAndName, path); return browserResultDto; } @@ -38,13 +36,14 @@ public class BrowserResultToBrowserResultDtoMapper { return fileObjectToFileObjectDtoMapper.map(fileObject, namespaceAndName, revision); } - private void addLinks(BrowserResult browserResult, BrowserResultDto dto, NamespaceAndName namespaceAndName) { + private void addLinks(BrowserResult browserResult, BrowserResultDto dto, NamespaceAndName namespaceAndName, String path) { + if (path.equals("/")) { + path = ""; + } if (browserResult.getRevision() == null) { - dto.add(Links.linkingTo().self(resourceLinks.source().selfWithoutRevision(namespaceAndName.getNamespace(), namespaceAndName.getName())).build()); + throw new IllegalStateException("missing revision in browser result for repository " + namespaceAndName + " and path " + path); } else { - dto.add(Links.linkingTo().self(resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), browserResult.getRevision())).build()); + dto.add(Links.linkingTo().self(resourceLinks.source().sourceWithPath(namespaceAndName.getNamespace(), namespaceAndName.getName(), browserResult.getRevision(), path)).build()); } } - - } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java index bc814c7e0c..fdcc5c56ca 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java @@ -10,7 +10,6 @@ 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 { @@ -27,19 +26,14 @@ public abstract class FileObjectToFileObjectDtoMapper extends BaseMapper tagName.equals(t.getName())) + .findFirst() + .orElseThrow(TagNotFoundException::new); + return Response.ok(tagToTagDtoMapper.map(tag, namespaceAndName)).build(); + } else { + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity("Error on getting tag from repository.") + .build(); + } + } } + private Tags getTags(RepositoryService repositoryService) throws IOException { + Repository repository = repositoryService.getRepository(); + RepositoryPermissions.read(repository).check(); + return repositoryService.getTagsCommand().getTags(); + } + + } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagToTagDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagToTagDtoMapper.java index ada0fa2887..917b4b7789 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagToTagDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagToTagDtoMapper.java @@ -11,6 +11,7 @@ import sonia.scm.repository.Tag; import javax.inject.Inject; +import static de.otto.edison.hal.Link.link; import static de.otto.edison.hal.Links.linkingTo; @Mapper @@ -25,7 +26,9 @@ public abstract class TagToTagDtoMapper { @AfterMapping void appendLinks(@MappingTarget TagDto target, @Context NamespaceAndName namespaceAndName) { Links.Builder linksBuilder = linkingTo() - .self(resourceLinks.tag().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getName())); + .self(resourceLinks.tag().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getName())) + .single(link("sources", resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision()))) + .single(link("changesets", resourceLinks.changeset().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision()))); target.add(linksBuilder.build()); } } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BrowserResultToBrowserResultDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BrowserResultToBrowserResultDtoMapperTest.java index bb7dadb566..528418a187 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BrowserResultToBrowserResultDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BrowserResultToBrowserResultDtoMapperTest.java @@ -18,8 +18,11 @@ import java.util.ArrayList; 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.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; public class BrowserResultToBrowserResultDtoMapperTest { @@ -60,6 +63,9 @@ public class BrowserResultToBrowserResultDtoMapperTest { fileObject2.setPath("/path/object/2"); fileObject2.setDescription("description of file object 2"); fileObject2.setDirectory(true); + + when(fileObjectToFileObjectDtoMapper.map(any(FileObject.class), any(NamespaceAndName.class), anyString())) + .thenReturn(new FileObjectDto()); } @After @@ -71,7 +77,7 @@ public class BrowserResultToBrowserResultDtoMapperTest { public void shouldMapAttributesCorrectly() { BrowserResult browserResult = createBrowserResult(); - BrowserResultDto dto = mapper.map(browserResult, new NamespaceAndName("foo", "bar")); + BrowserResultDto dto = mapper.map(browserResult, new NamespaceAndName("foo", "bar"), "path"); assertEqualAttributes(browserResult, dto); } @@ -81,17 +87,25 @@ public class BrowserResultToBrowserResultDtoMapperTest { BrowserResult browserResult = createBrowserResult(); NamespaceAndName namespaceAndName = new NamespaceAndName("foo", "bar"); - BrowserResultDto dto = mapper.map(browserResult, namespaceAndName); + BrowserResultDto dto = mapper.map(browserResult, namespaceAndName, "path"); verify(fileObjectToFileObjectDtoMapper).map(fileObject1, namespaceAndName, "Revision"); verify(fileObjectToFileObjectDtoMapper).map(fileObject2, namespaceAndName, "Revision"); } + @Test + public void shouldSetLinksCorrectly() { + BrowserResult browserResult = createBrowserResult(); + NamespaceAndName namespaceAndName = new NamespaceAndName("foo", "bar"); + + BrowserResultDto dto = mapper.map(browserResult, namespaceAndName, "path"); + + assertThat(dto.getLinks().getLinkBy("self").get().getHref()).contains("path"); + } + private BrowserResult createBrowserResult() { BrowserResult browserResult = new BrowserResult(); - browserResult.setTag("Tag"); browserResult.setRevision("Revision"); - browserResult.setBranch("Branch"); browserResult.setFiles(createFileObjects()); return browserResult; @@ -106,8 +120,6 @@ public class BrowserResultToBrowserResultDtoMapperTest { } 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()); } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java index 570830d651..40aa61852a 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java @@ -58,7 +58,7 @@ public class ChangesetRootResourceTest { private RepositoryServiceFactory serviceFactory; @Mock - private RepositoryService service; + private RepositoryService repositoryService; @Mock private LogCommandBuilder logCommandBuilder; @@ -83,12 +83,12 @@ public class ChangesetRootResourceTest { .of(new RepositoryResource(null, null, null, null, null, MockProvider.of(changesetRootResource), null, null, null, null)), null); dispatcher.getRegistry().addSingletonResource(repositoryRootResource); - when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service); - when(serviceFactory.create(any(Repository.class))).thenReturn(service); - when(service.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo")); + when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(repositoryService); + when(serviceFactory.create(any(Repository.class))).thenReturn(repositoryService); + when(repositoryService.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo")); dispatcher.getProviderFactory().registerProvider(NotFoundExceptionMapper.class); dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class); - when(service.getLogCommand()).thenReturn(logCommandBuilder); + when(repositoryService.getLogCommand()).thenReturn(logCommandBuilder); subjectThreadState.bind(); ThreadContext.bind(subject); when(subject.isPermitted(any(String.class))).thenReturn(true); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java index 6fca4b8562..2c4893b362 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java @@ -88,8 +88,6 @@ public class SourceRootResourceTest { 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\":"); } @@ -106,9 +104,7 @@ public class SourceRootResourceTest { @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!"); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/TagRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/TagRootResourceTest.java new file mode 100644 index 0000000000..92d11b3895 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/TagRootResourceTest.java @@ -0,0 +1,199 @@ +package sonia.scm.api.v2.resources; + +import lombok.extern.slf4j.Slf4j; +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.assertj.core.util.Lists; +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.After; +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.api.rest.AuthorizationExceptionMapper; +import sonia.scm.repository.NamespaceAndName; +import sonia.scm.repository.Repository; +import sonia.scm.repository.Tag; +import sonia.scm.repository.Tags; +import sonia.scm.repository.api.RepositoryService; +import sonia.scm.repository.api.RepositoryServiceFactory; +import sonia.scm.repository.api.TagsCommandBuilder; +import sonia.scm.web.VndMediaType; + +import java.net.URI; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@Slf4j +@RunWith(MockitoJUnitRunner.Silent.class) +public class TagRootResourceTest { + + public static final String TAG_PATH = "space/repo/tags/"; + public static final String TAG_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + TAG_PATH; + 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 repositoryService; + + @Mock + private TagsCommandBuilder tagsCommandBuilder; + + private TagCollectionToDtoMapper tagCollectionToDtoMapper; + + @InjectMocks + private TagToTagDtoMapperImpl tagToTagDtoMapper; + + private TagRootResource tagRootResource; + + + private final Subject subject = mock(Subject.class); + private final ThreadState subjectThreadState = new SubjectThreadState(subject); + + + @Before + public void prepareEnvironment() throws Exception { + tagCollectionToDtoMapper = new TagCollectionToDtoMapper(resourceLinks, tagToTagDtoMapper); + tagRootResource = new TagRootResource(serviceFactory, tagCollectionToDtoMapper, tagToTagDtoMapper); + RepositoryRootResource repositoryRootResource = new RepositoryRootResource(MockProvider + .of(new RepositoryResource(null, null, null, MockProvider.of(tagRootResource), null, + null, null, null, null, null)), null); + dispatcher.getRegistry().addSingletonResource(repositoryRootResource); + when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(repositoryService); + when(serviceFactory.create(any(Repository.class))).thenReturn(repositoryService); + when(repositoryService.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo")); + dispatcher.getProviderFactory().registerProvider(NotFoundExceptionMapper.class); + dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class); + when(repositoryService.getTagsCommand()).thenReturn(tagsCommandBuilder); + subjectThreadState.bind(); + ThreadContext.bind(subject); + when(subject.isPermitted(any(String.class))).thenReturn(true); + } + + @After + public void cleanupContext() { + ThreadContext.unbindSubject(); + } + + @Test + public void shouldGet404OnMissingTag() throws Exception { + Tags tags = new Tags(); + tags.setTags(Lists.emptyList()); + when(tagsCommandBuilder.getTags()).thenReturn(tags); + + MockHttpRequest request = MockHttpRequest + .get(TAG_URL + "not_existing_tag") + .accept(VndMediaType.TAG); + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + assertEquals(404, response.getStatus()); + } + + @Test + public void shouldGetEmptyTagListOnMissingTags() throws Exception { + Tags tags = new Tags(); + tags.setTags(Lists.emptyList()); + when(tagsCommandBuilder.getTags()).thenReturn(tags); + + MockHttpRequest request = MockHttpRequest + .get(TAG_URL) + .accept(VndMediaType.TAG_COLLECTION); + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + assertEquals(200, response.getStatus()); + assertThat(response).isNotNull(); + assertThat(response.getContentAsString()) + .isNotBlank() + .contains("_links"); + } + + @Test + public void shouldGet500OnTagCommandError() throws Exception { + when(tagsCommandBuilder.getTags()).thenReturn(null); + + MockHttpRequest request = MockHttpRequest + .get(TAG_URL + "not_existing_tag") + .accept(VndMediaType.TAG); + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + assertEquals(500, response.getStatus()); + + request = MockHttpRequest + .get(TAG_URL) + .accept(VndMediaType.TAG_COLLECTION); + response = new MockHttpResponse(); + dispatcher.invoke(request, response); + assertEquals(500, response.getStatus()); + } + + + @Test + public void shouldGetTags() throws Exception { + Tags tags = new Tags(); + String tag1 = "v1.0"; + String revision1 = "revision_1234"; + String tag2 = "v2.0"; + String revision2 = "revision_12345"; + tags.setTags(Lists.newArrayList(new Tag(tag1, revision1), new Tag(tag2, revision2))); + when(tagsCommandBuilder.getTags()).thenReturn(tags); + + MockHttpRequest request = MockHttpRequest + .get(TAG_URL) + .accept(VndMediaType.TAG_COLLECTION); + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + assertEquals(200, response.getStatus()); + log.info("the content: ", response.getContentAsString()); + assertTrue(response.getContentAsString().contains(String.format("\"name\":\"%s\"", tag1))); + assertTrue(response.getContentAsString().contains(String.format("\"revision\":\"%s\"", revision1))); + assertTrue(response.getContentAsString().contains(String.format("\"name\":\"%s\"", tag2))); + assertTrue(response.getContentAsString().contains(String.format("\"revision\":\"%s\"", revision2))); + } + + @Test + public void shouldGetTag() throws Exception { + Tags tags = new Tags(); + String tag1 = "v1.0"; + String revision1 = "revision_1234"; + String tag2 = "v2.0"; + String revision2 = "revision_12345"; + tags.setTags(Lists.newArrayList(new Tag(tag1, revision1), new Tag(tag2, revision2))); + when(tagsCommandBuilder.getTags()).thenReturn(tags); + + MockHttpRequest request = MockHttpRequest + .get(TAG_URL + tag1) + .accept(VndMediaType.TAG); + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + assertEquals(200, response.getStatus()); + assertTrue(response.getContentAsString().contains(String.format("\"name\":\"%s\"", tag1))); + assertTrue(response.getContentAsString().contains(String.format("\"revision\":\"%s\"", revision1))); + + request = MockHttpRequest + .get(TAG_URL + tag2) + .accept(VndMediaType.TAG); + response = new MockHttpResponse(); + dispatcher.invoke(request, response); + assertEquals(200, response.getStatus()); + log.info("the content: ", response.getContentAsString()); + assertTrue(response.getContentAsString().contains(String.format("\"name\":\"%s\"", tag2))); + assertTrue(response.getContentAsString().contains(String.format("\"revision\":\"%s\"", revision2))); + } +}