diff --git a/scm-it/src/test/java/sonia/scm/it/AutoCompleteITCase.java b/scm-it/src/test/java/sonia/scm/it/AutoCompleteITCase.java new file mode 100644 index 0000000000..f343f322a3 --- /dev/null +++ b/scm-it/src/test/java/sonia/scm/it/AutoCompleteITCase.java @@ -0,0 +1,73 @@ +package sonia.scm.it; + +import org.junit.Before; +import org.junit.Test; +import sonia.scm.it.utils.ScmRequests; +import sonia.scm.it.utils.TestData; + +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.stream.IntStream; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AutoCompleteITCase { + + + public static final String CREATED_USER_PREFIX = "user_"; + public static final String CREATED_GROUP_PREFIX = "group_"; + + @Before + public void init() { + TestData.cleanup(); + } + + @Test + public void adminShouldAutoComplete() { + shouldAutocomplete(TestData.USER_SCM_ADMIN, TestData.USER_SCM_ADMIN); + } + + @Test + public void userShouldAutoComplete() { + String username = "nonAdmin"; + String password = "pass"; + TestData.createUser(username, password, false, "xml", "email@e.de"); + shouldAutocomplete(username, password); + } + + public void shouldAutocomplete(String username, String password) { + createUsers(); + createGroups(); + ScmRequests.start() + .requestIndexResource(username, password) + .assertStatusCode(200) + .requestAutoCompleteGroups("group*") + .assertStatusCode(200) + .assertAutoCompleteResults(assertAutoCompleteResult(CREATED_GROUP_PREFIX)) + .returnToPrevious() + .requestAutoCompleteUsers("user*") + .assertStatusCode(200) + .assertAutoCompleteResults(assertAutoCompleteResult(CREATED_USER_PREFIX)); + } + + @SuppressWarnings("unchecked") + private Consumer> assertAutoCompleteResult(String id) { + return autoCompleteDtos -> { + IntStream.range(0, 5).forEach(i -> { + assertThat(autoCompleteDtos).as("return maximum 5 entries").hasSize(5); + assertThat(autoCompleteDtos.get(i)).containsEntry("id", id + (i + 1)); + assertThat(autoCompleteDtos.get(i)).containsEntry("displayName", id + (i + 1)); + }); + }; + } + + private void createUsers() { + IntStream.range(0, 6).forEach(i -> TestData.createUser(CREATED_USER_PREFIX + (i + 1), "pass", false, "xml", CREATED_USER_PREFIX + (i + 1) + "@scm-manager.org")); + } + + private void createGroups() { + IntStream.range(0, 6).forEach(i -> TestData.createGroup(CREATED_GROUP_PREFIX + (i + 1), CREATED_GROUP_PREFIX + (i + 1))); + } + +} diff --git a/scm-it/src/test/java/sonia/scm/it/MeITCase.java b/scm-it/src/test/java/sonia/scm/it/MeITCase.java index 64f06765ea..5809f883ca 100644 --- a/scm-it/src/test/java/sonia/scm/it/MeITCase.java +++ b/scm-it/src/test/java/sonia/scm/it/MeITCase.java @@ -20,12 +20,9 @@ public class MeITCase { String newPassword = TestData.USER_SCM_ADMIN + "1"; // admin change the own password ScmRequests.start() - .given() - .url(TestData.getMeUrl()) - .usernameAndPassword(TestData.USER_SCM_ADMIN, TestData.USER_SCM_ADMIN) - .getMeResource() + .requestIndexResource(TestData.USER_SCM_ADMIN, TestData.USER_SCM_ADMIN) + .requestMe() .assertStatusCode(200) - .usingMeResponse() .assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.TRUE)) .assertPassword(Assert::assertNull) .assertType(s -> assertThat(s).isEqualTo("xml")) @@ -33,12 +30,9 @@ public class MeITCase { .assertStatusCode(204); // assert password is changed -> login with the new Password than undo changes ScmRequests.start() - .given() - .url(TestData.getUserUrl(TestData.USER_SCM_ADMIN)) - .usernameAndPassword(TestData.USER_SCM_ADMIN, newPassword) - .getMeResource() + .requestIndexResource(TestData.USER_SCM_ADMIN, newPassword) + .requestMe() .assertStatusCode(200) - .usingMeResponse() .assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.TRUE))// still admin .requestChangePassword(newPassword, TestData.USER_SCM_ADMIN) .assertStatusCode(204); @@ -49,14 +43,11 @@ public class MeITCase { String newUser = "user"; String password = "pass"; String type = "not XML Type"; - TestData.createUser(newUser, password, true, type); + TestData.createUser(newUser, password, true, type, "user@scm-manager.org"); ScmRequests.start() - .given() - .url(TestData.getMeUrl()) - .usernameAndPassword(newUser, password) - .getMeResource() + .requestIndexResource(newUser, password) + .requestMe() .assertStatusCode(200) - .usingMeResponse() .assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.TRUE)) .assertPassword(Assert::assertNull) .assertType(s -> assertThat(s).isEqualTo(type)) diff --git a/scm-it/src/test/java/sonia/scm/it/PermissionsITCase.java b/scm-it/src/test/java/sonia/scm/it/PermissionsITCase.java index f288d4891c..8785f1d8ce 100644 --- a/scm-it/src/test/java/sonia/scm/it/PermissionsITCase.java +++ b/scm-it/src/test/java/sonia/scm/it/PermissionsITCase.java @@ -87,13 +87,13 @@ public class PermissionsITCase { @Before public void prepareEnvironment() { TestData.createDefault(); - TestData.createUser(USER_READ, USER_PASS); + TestData.createNotAdminUser(USER_READ, USER_PASS); TestData.createUserPermission(USER_READ, PermissionType.READ, repositoryType); - TestData.createUser(USER_WRITE, USER_PASS); + TestData.createNotAdminUser(USER_WRITE, USER_PASS); TestData.createUserPermission(USER_WRITE, PermissionType.WRITE, repositoryType); - TestData.createUser(USER_OWNER, USER_PASS); + TestData.createNotAdminUser(USER_OWNER, USER_PASS); TestData.createUserPermission(USER_OWNER, PermissionType.OWNER, repositoryType); - TestData.createUser(USER_OTHER, USER_PASS); + TestData.createNotAdminUser(USER_OTHER, USER_PASS); createdPermissions = 3; } 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 7dd548f452..66ebc57c90 100644 --- a/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java +++ b/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java @@ -44,7 +44,7 @@ public class RepositoryAccessITCase { private final String repositoryType; private File folder; - private ScmRequests.AppliedRepositoryRequest repositoryGetRequest; + private ScmRequests.RepositoryResponse repositoryResponse; public RepositoryAccessITCase(String repositoryType) { this.repositoryType = repositoryType; @@ -59,17 +59,13 @@ public class RepositoryAccessITCase { public void init() { TestData.createDefault(); folder = tempFolder.getRoot(); - repositoryGetRequest = ScmRequests.start() - .given() - .url(TestData.getDefaultRepositoryUrl(repositoryType)) - .usernameAndPassword(ADMIN_USERNAME, ADMIN_PASSWORD) - .getRepositoryResource() + String namespace = ADMIN_USERNAME; + String repo = TestData.getDefaultRepoName(repositoryType); + repositoryResponse = + ScmRequests.start() + .requestIndexResource(ADMIN_USERNAME, ADMIN_PASSWORD) + .requestRepository(namespace, repo) .assertStatusCode(HttpStatus.SC_OK); - ScmRequests.AppliedMeRequest meGetRequest = ScmRequests.start() - .given() - .url(TestData.getMeUrl()) - .usernameAndPassword(ADMIN_USERNAME, ADMIN_PASSWORD) - .getMeResource(); } @Test @@ -306,17 +302,12 @@ public class RepositoryAccessITCase { public void shouldFindFileHistory() throws IOException { RepositoryClient repositoryClient = RepositoryUtil.createRepositoryClient(repositoryType, folder); Changeset changeset = RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, "folder/subfolder/a.txt", "a"); - repositoryGetRequest - .usingRepositoryResponse() + repositoryResponse .requestSources() - .usingSourcesResponse() .requestSelf("folder") - .usingSourcesResponse() .requestSelf("subfolder") - .usingSourcesResponse() .requestFileHistory("a.txt") .assertStatusCode(HttpStatus.SC_OK) - .usingChangesetsResponse() .assertChangesets(changesets -> { assertThat(changesets).hasSize(1); assertThat(changesets.get(0)).containsEntry("id", changeset.getId()); @@ -332,14 +323,11 @@ public class RepositoryAccessITCase { String fileName = "a.txt"; Changeset changeset = RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, fileName, "a"); String revision = changeset.getId(); - repositoryGetRequest - .usingRepositoryResponse() + repositoryResponse .requestChangesets() .assertStatusCode(HttpStatus.SC_OK) - .usingChangesetsResponse() .requestModifications(revision) .assertStatusCode(HttpStatus.SC_OK) - .usingModificationsResponse() .assertRevision(actualRevision -> assertThat(actualRevision).isEqualTo(revision)) .assertAdded(addedFiles -> assertThat(addedFiles) .hasSize(1) @@ -359,14 +347,11 @@ public class RepositoryAccessITCase { Changeset changeset = RepositoryUtil.removeAndCommitFile(repositoryClient, ADMIN_USERNAME, fileName); String revision = changeset.getId(); - repositoryGetRequest - .usingRepositoryResponse() + repositoryResponse .requestChangesets() .assertStatusCode(HttpStatus.SC_OK) - .usingChangesetsResponse() .requestModifications(revision) .assertStatusCode(HttpStatus.SC_OK) - .usingModificationsResponse() .assertRevision(actualRevision -> assertThat(actualRevision).isEqualTo(revision)) .assertRemoved(removedFiles -> assertThat(removedFiles) .hasSize(1) @@ -386,14 +371,11 @@ public class RepositoryAccessITCase { Changeset changeset = RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, fileName, "new Content"); String revision = changeset.getId(); - repositoryGetRequest - .usingRepositoryResponse() + repositoryResponse .requestChangesets() .assertStatusCode(HttpStatus.SC_OK) - .usingChangesetsResponse() .requestModifications(revision) .assertStatusCode(HttpStatus.SC_OK) - .usingModificationsResponse() .assertRevision(actualRevision -> assertThat(actualRevision).isEqualTo(revision)) .assertModified(modifiedFiles -> assertThat(modifiedFiles) .hasSize(1) @@ -423,14 +405,11 @@ public class RepositoryAccessITCase { Changeset changeset = RepositoryUtil.commitMultipleFileModifications(repositoryClient, ADMIN_USERNAME, addedFiles, modifiedFiles, removedFiles); String revision = changeset.getId(); - repositoryGetRequest - .usingRepositoryResponse() + repositoryResponse .requestChangesets() .assertStatusCode(HttpStatus.SC_OK) - .usingChangesetsResponse() .requestModifications(revision) .assertStatusCode(HttpStatus.SC_OK) - .usingModificationsResponse() .assertRevision(actualRevision -> assertThat(actualRevision).isEqualTo(revision)) .assertAdded(a -> assertThat(a) .hasSize(1) @@ -463,14 +442,11 @@ public class RepositoryAccessITCase { Changeset changeset = RepositoryUtil.commitMultipleFileModifications(repositoryClient, ADMIN_USERNAME, addedFiles, modifiedFiles, removedFiles); String revision = changeset.getId(); - repositoryGetRequest - .usingRepositoryResponse() + repositoryResponse .requestChangesets() .assertStatusCode(HttpStatus.SC_OK) - .usingChangesetsResponse() .requestModifications(revision) .assertStatusCode(HttpStatus.SC_OK) - .usingModificationsResponse() .assertRevision(actualRevision -> assertThat(actualRevision).isEqualTo(revision)) .assertAdded(a -> assertThat(a) .hasSize(3) diff --git a/scm-it/src/test/java/sonia/scm/it/UserITCase.java b/scm-it/src/test/java/sonia/scm/it/UserITCase.java index 33fbe0cc5d..a4bc4e412f 100644 --- a/scm-it/src/test/java/sonia/scm/it/UserITCase.java +++ b/scm-it/src/test/java/sonia/scm/it/UserITCase.java @@ -19,57 +19,48 @@ public class UserITCase { public void adminShouldChangeOwnPassword() { String newUser = "user"; String password = "pass"; - TestData.createUser(newUser, password, true, "xml"); + TestData.createUser(newUser, password, true, "xml", "user@scm-manager.org"); String newPassword = "new_password"; // admin change the own password ScmRequests.start() - .given() - .url(TestData.getUserUrl(newUser)) - .usernameAndPassword(newUser, password) - .getUserResource() + .requestIndexResource(newUser, password) + .assertStatusCode(200) + .requestUser(newUser) .assertStatusCode(200) - .usingUserResponse() .assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.TRUE)) .assertPassword(Assert::assertNull) .requestChangePassword(newPassword) // the oldPassword is not needed in the user resource .assertStatusCode(204); // assert password is changed -> login with the new Password ScmRequests.start() - .given() - .url(TestData.getUserUrl(newUser)) - .usernameAndPassword(newUser, newPassword) - .getUserResource() + .requestIndexResource(newUser, newPassword) .assertStatusCode(200) - .usingUserResponse() + .requestUser(newUser) .assertAdmin(isAdmin -> assertThat(isAdmin).isEqualTo(Boolean.TRUE)) .assertPassword(Assert::assertNull); - } @Test public void adminShouldChangePasswordOfOtherUser() { String newUser = "user"; String password = "pass"; - TestData.createUser(newUser, password, true, "xml"); + TestData.createUser(newUser, password, true, "xml", "user@scm-manager.org"); String newPassword = "new_password"; // admin change the password of the user ScmRequests.start() - .given() - .url(TestData.getUserUrl(newUser))// the admin get the user object - .usernameAndPassword(TestData.USER_SCM_ADMIN, TestData.USER_SCM_ADMIN) - .getUserResource() + .requestIndexResource(TestData.USER_SCM_ADMIN, TestData.USER_SCM_ADMIN) + .assertStatusCode(200) + .requestUser(newUser) .assertStatusCode(200) - .usingUserResponse() .assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.TRUE)) // the user anonymous is not an admin .assertPassword(Assert::assertNull) .requestChangePassword(newPassword) // the oldPassword is not needed in the user resource .assertStatusCode(204); // assert password is changed ScmRequests.start() - .given() - .url(TestData.getUserUrl(newUser)) - .usernameAndPassword(newUser, newPassword) - .getUserResource() + .requestIndexResource(newUser, newPassword) + .assertStatusCode(200) + .requestUser(newUser) .assertStatusCode(200); } @@ -80,14 +71,12 @@ public class UserITCase { String newUser = "user"; String password = "pass"; String type = "not XML Type"; - TestData.createUser(newUser, password, true, type); + TestData.createUser(newUser, password, true, type, "user@scm-manager.org"); ScmRequests.start() - .given() - .url(TestData.getMeUrl()) - .usernameAndPassword(newUser, password) - .getUserResource() + .requestIndexResource(newUser, password) + .assertStatusCode(200) + .requestUser(newUser) .assertStatusCode(200) - .usingUserResponse() .assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.TRUE)) .assertPassword(Assert::assertNull) .assertType(s -> assertThat(s).isEqualTo(type)) diff --git a/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java b/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java index c1b6ec85d4..70e0d2a896 100644 --- a/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java +++ b/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java @@ -2,11 +2,10 @@ package sonia.scm.it.utils; import io.restassured.RestAssured; import io.restassured.response.Response; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.junit.Assert; import sonia.scm.web.VndMediaType; -import java.net.URI; +import java.net.ConnectException; import java.util.List; import java.util.Map; import java.util.function.Consumer; @@ -27,8 +26,6 @@ import static sonia.scm.it.utils.TestData.createPasswordChangeJson; */ public class ScmRequests { - private static final Logger LOG = LoggerFactory.getLogger(ScmRequests.class); - private String url; private String username; private String password; @@ -37,8 +34,10 @@ public class ScmRequests { return new ScmRequests(); } - public Given given() { - return new Given(); + public IndexResponse requestIndexResource(String username, String password) { + setUsername(username); + setPassword(password); + return new IndexResponse(applyGETRequest(RestUtil.REST_BASE_URL.toString())); } @@ -50,26 +49,53 @@ public class ScmRequests { * @return the response of the GET request using the given link */ private Response applyGETRequestFromLink(Response response, String linkPropertyName) { - String result = response + return applyGETRequestFromLinkWithParams(response, linkPropertyName, ""); + } + + /** + * Apply a GET Request to the extracted url from the given link + * + * @param linkPropertyName the property name of link + * @param response the response containing the link + * @param params query params eg. ?q=xyz&count=12 or path params eg. namespace/name + * @return the response of the GET request using the given link + */ + private Response applyGETRequestFromLinkWithParams(Response response, String linkPropertyName, String params) { + String url = response .then() .extract() .path(linkPropertyName); - LOG.info("Extracted result {} from response: {}", linkPropertyName, result); - return applyGETRequest(result); + Assert.assertNotNull("no url found for link " + linkPropertyName, url); + return applyGETRequestWithQueryParams(url, params); } + /** + * Apply a GET Request to the given url and return the response. + * + * @param url the url of the GET request + * @param params query params eg. ?q=xyz&count=12 or path params eg. namespace/name + * @return the response of the GET request using the given url + */ + private Response applyGETRequestWithQueryParams(String url, String params) { + try { + return RestAssured.given() + .auth().preemptive().basic(username, password) + .when() + .get(url + params); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } /** * Apply a GET Request to the given url and return the response. * * @param url the url of the GET request * @return the response of the GET request using the given url - */ + **/ private Response applyGETRequest(String url) { - return RestAssured.given() - .auth().preemptive().basic(username, password) - .when() - .get(url); + return applyGETRequestWithQueryParams(url, ""); } @@ -107,11 +133,6 @@ public class ScmRequests { .put(url); } - - private void setUrl(String url) { - this.url = url; - } - private void setUsername(String username) { this.username = username; } @@ -120,284 +141,185 @@ public class ScmRequests { this.password = password; } - private String getUrl() { - return url; - } - private String getUsername() { - return username; - } + public class IndexResponse extends ModelResponse { + public static final String LINK_AUTOCOMPLETE_USERS = "_links.autocomplete.find{it.name=='users'}.href"; + public static final String LINK_AUTOCOMPLETE_GROUPS = "_links.autocomplete.find{it.name=='groups'}.href"; + public static final String LINK_REPOSITORIES = "_links.repositories.href"; + private static final String LINK_ME = "_links.me.href"; + private static final String LINK_USERS = "_links.users.href"; - private String getPassword() { - return password; - } - - public class Given { - - public GivenUrl url(String url) { - setUrl(url); - return new GivenUrl(); + public IndexResponse(Response response) { + super(response, null); } - public GivenUrl url(URI url) { - setUrl(url.toString()); - return new GivenUrl(); + public AutoCompleteResponse requestAutoCompleteUsers(String q) { + return new AutoCompleteResponse<>(applyGETRequestFromLinkWithParams(response, LINK_AUTOCOMPLETE_USERS, "?q=" + q), this); + } + + public AutoCompleteResponse requestAutoCompleteGroups(String q) { + return new AutoCompleteResponse<>(applyGETRequestFromLinkWithParams(response, LINK_AUTOCOMPLETE_GROUPS, "?q=" + q), this); + } + + public RepositoryResponse requestRepository(String namespace, String name) { + return new RepositoryResponse<>(applyGETRequestFromLinkWithParams(response, LINK_REPOSITORIES, namespace + "/" + name), this); + } + + public MeResponse requestMe() { + return new MeResponse<>(applyGETRequestFromLink(response, LINK_ME), this); + } + + public UserResponse requestUser(String username) { + return new UserResponse<>(applyGETRequestFromLinkWithParams(response, LINK_USERS, username), this); } } - public class GivenWithUrlAndAuth { - public AppliedMeRequest getMeResource() { - return new AppliedMeRequest(applyGETRequest(url)); + public class RepositoryResponse extends ModelResponse, PREV> { + + + public static final String LINKS_SOURCES = "_links.sources.href"; + public static final String LINKS_CHANGESETS = "_links.changesets.href"; + + public RepositoryResponse(Response response, PREV previousResponse) { + super(response, previousResponse); } - public AppliedUserRequest getUserResource() { - return new AppliedUserRequest(applyGETRequest(url)); + public SourcesResponse requestSources() { + return new SourcesResponse<>(applyGETRequestFromLink(response, LINKS_SOURCES), this); } - public AppliedRepositoryRequest getRepositoryResource() { - return new AppliedRepositoryRequest( - applyGETRequest(url) - ); - } - } - - public class AppliedRequest { - private Response response; - - public AppliedRequest(Response response) { - this.response = response; - } - - /** - * apply custom assertions to the actual response - * - * @param consumer consume the response in order to assert the content. the header, the payload etc.. - * @return the self object - */ - public SELF assertResponse(Consumer consumer) { - consumer.accept(response); - return (SELF) this; - } - - /** - * special assertion of the status code - * - * @param expectedStatusCode the expected status code - * @return the self object - */ - public SELF assertStatusCode(int expectedStatusCode) { - this.response.then().assertThat().statusCode(expectedStatusCode); - return (SELF) this; + public ChangesetsResponse requestChangesets() { + return new ChangesetsResponse<>(applyGETRequestFromLink(response, LINKS_CHANGESETS), this); } } - public class AppliedRepositoryRequest extends AppliedRequest { + public class ChangesetsResponse extends ModelResponse, PREV> { - public AppliedRepositoryRequest(Response response) { - super(response); - } - - public RepositoryResponse usingRepositoryResponse() { - return new RepositoryResponse(super.response); - } - } - - public class RepositoryResponse { - - private Response repositoryResponse; - - public RepositoryResponse(Response repositoryResponse) { - this.repositoryResponse = repositoryResponse; - } - - public AppliedSourcesRequest requestSources() { - return new AppliedSourcesRequest(applyGETRequestFromLink(repositoryResponse, "_links.sources.href")); - } - - public AppliedChangesetsRequest requestChangesets() { - return new AppliedChangesetsRequest(applyGETRequestFromLink(repositoryResponse, "_links.changesets.href")); - } - } - - public class AppliedChangesetsRequest extends AppliedRequest { - - public AppliedChangesetsRequest(Response response) { - super(response); - } - - public ChangesetsResponse usingChangesetsResponse() { - return new ChangesetsResponse(super.response); - } - } - - public class ChangesetsResponse { - private Response changesetsResponse; - - public ChangesetsResponse(Response changesetsResponse) { - this.changesetsResponse = changesetsResponse; + public ChangesetsResponse(Response response, PREV previousResponse) { + super(response, previousResponse); } public ChangesetsResponse assertChangesets(Consumer> changesetsConsumer) { - List changesets = changesetsResponse.then().extract().path("_embedded.changesets"); + List changesets = response.then().extract().path("_embedded.changesets"); changesetsConsumer.accept(changesets); return this; } - public AppliedDiffRequest requestDiff(String revision) { - return new AppliedDiffRequest(applyGETRequestFromLink(changesetsResponse, "_embedded.changesets.find{it.id=='" + revision + "'}._links.diff.href")); + public DiffResponse requestDiff(String revision) { + return new DiffResponse<>(applyGETRequestFromLink(response, "_embedded.changesets.find{it.id=='" + revision + "'}._links.diff.href"), this); } - public AppliedModificationsRequest requestModifications(String revision) { - return new AppliedModificationsRequest(applyGETRequestFromLink(changesetsResponse, "_embedded.changesets.find{it.id=='" + revision + "'}._links.modifications.href")); + public ModificationsResponse requestModifications(String revision) { + return new ModificationsResponse<>(applyGETRequestFromLink(response, "_embedded.changesets.find{it.id=='" + revision + "'}._links.modifications.href"), this); } } - public class AppliedSourcesRequest extends AppliedRequest { - public AppliedSourcesRequest(Response sourcesResponse) { - super(sourcesResponse); + public class SourcesResponse extends ModelResponse, PREV> { + + public SourcesResponse(Response response, PREV previousResponse) { + super(response, previousResponse); } - public SourcesResponse usingSourcesResponse() { - return new SourcesResponse(super.response); - } - } - - public class SourcesResponse { - - private Response sourcesResponse; - - public SourcesResponse(Response sourcesResponse) { - this.sourcesResponse = sourcesResponse; - } - - public AppliedChangesetsRequest requestFileHistory(String fileName) { - return new AppliedChangesetsRequest(applyGETRequestFromLink(sourcesResponse, "_embedded.children.find{it.name=='" + fileName + "'}._links.history.href")); - } - - public AppliedSourcesRequest requestSelf(String fileName) { - return new AppliedSourcesRequest(applyGETRequestFromLink(sourcesResponse, "_embedded.children.find{it.name=='" + fileName + "'}._links.self.href")); - } - } - - public class AppliedDiffRequest extends AppliedRequest { - - public AppliedDiffRequest(Response response) { - super(response); - } - } - - public class GivenUrl { - - public GivenWithUrlAndAuth usernameAndPassword(String username, String password) { - setUsername(username); - setPassword(password); - return new GivenWithUrlAndAuth(); - } - } - - public class AppliedModificationsRequest extends AppliedRequest { - public AppliedModificationsRequest(Response response) { - super(response); - } - - public ModificationsResponse usingModificationsResponse() { - return new ModificationsResponse(super.response); - } - - } - - public class ModificationsResponse { - private Response resource; - - public ModificationsResponse(Response resource) { - this.resource = resource; - } - - public ModificationsResponse assertRevision(Consumer assertRevision) { - String revision = resource.then().extract().path("revision"); + public SourcesResponse assertRevision(Consumer assertRevision) { + String revision = response.then().extract().path("revision"); assertRevision.accept(revision); return this; } - public ModificationsResponse assertAdded(Consumer> assertAdded) { - List added = resource.then().extract().path("added"); + public SourcesResponse assertFiles(Consumer assertFiles) { + List files = response.then().extract().path("files"); + assertFiles.accept(files); + return this; + } + + public ChangesetsResponse requestFileHistory(String fileName) { + return new ChangesetsResponse<>(applyGETRequestFromLink(response, "_embedded.children.find{it.name=='" + fileName + "'}._links.history.href"), this); + } + + public SourcesResponse requestSelf(String fileName) { + return new SourcesResponse<>(applyGETRequestFromLink(response, "_embedded.children.find{it.name=='" + fileName + "'}._links.self.href"), this); + } + } + + public class ModificationsResponse extends ModelResponse, PREV> { + + public ModificationsResponse(Response response, PREV previousResponse) { + super(response, previousResponse); + } + + public ModificationsResponse assertRevision(Consumer assertRevision) { + String revision = response.then().extract().path("revision"); + assertRevision.accept(revision); + return this; + } + + public ModificationsResponse assertAdded(Consumer> assertAdded) { + List added = response.then().extract().path("added"); assertAdded.accept(added); return this; } - public ModificationsResponse assertRemoved(Consumer> assertRemoved) { - List removed = resource.then().extract().path("removed"); + public ModificationsResponse assertRemoved(Consumer> assertRemoved) { + List removed = response.then().extract().path("removed"); assertRemoved.accept(removed); return this; } - public ModificationsResponse assertModified(Consumer> assertModified) { - List modified = resource.then().extract().path("modified"); + public ModificationsResponse assertModified(Consumer> assertModified) { + List modified = response.then().extract().path("modified"); assertModified.accept(modified); return this; } } - public class AppliedMeRequest extends AppliedRequest { - - public AppliedMeRequest(Response response) { - super(response); - } - - public MeResponse usingMeResponse() { - return new MeResponse(super.response); - } - - } - - public class MeResponse extends UserResponse { + public class MeResponse extends UserResponse { - public MeResponse(Response response) { - super(response); - } - - public AppliedChangePasswordRequest requestChangePassword(String oldPassword, String newPassword) { - return new AppliedChangePasswordRequest(applyPUTRequestFromLink(super.response, "_links.password.href", VndMediaType.PASSWORD_CHANGE, createPasswordChangeJson(oldPassword, newPassword))); + public MeResponse(Response response, PREV previousResponse) { + super(response, previousResponse); } } - public class UserResponse extends ModelResponse { + public class UserResponse extends ModelResponse, PREV> { public static final String LINKS_PASSWORD_HREF = "_links.password.href"; - public UserResponse(Response response) { - super(response); + public UserResponse(Response response, PREV previousResponse) { + super(response, previousResponse); } - public SELF assertPassword(Consumer assertPassword) { + public UserResponse assertPassword(Consumer assertPassword) { return super.assertSingleProperty(assertPassword, "password"); } - public SELF assertType(Consumer assertType) { + public UserResponse assertType(Consumer assertType) { return assertSingleProperty(assertType, "type"); } - public SELF assertAdmin(Consumer assertAdmin) { + public UserResponse assertAdmin(Consumer assertAdmin) { return assertSingleProperty(assertAdmin, "admin"); } - public SELF assertPasswordLinkDoesNotExists() { + public UserResponse assertPasswordLinkDoesNotExists() { return assertPropertyPathDoesNotExists(LINKS_PASSWORD_HREF); } - public SELF assertPasswordLinkExists() { + public UserResponse assertPasswordLinkExists() { return assertPropertyPathExists(LINKS_PASSWORD_HREF); } - public AppliedChangePasswordRequest requestChangePassword(String newPassword) { - return new AppliedChangePasswordRequest(applyPUTRequestFromLink(super.response, LINKS_PASSWORD_HREF, VndMediaType.PASSWORD_CHANGE, createPasswordChangeJson(null, newPassword))); + public ChangePasswordResponse requestChangePassword(String newPassword) { + return requestChangePassword(null, newPassword); + } + + public ChangePasswordResponse requestChangePassword(String oldPassword, String newPassword) { + return new ChangePasswordResponse<>(applyPUTRequestFromLink(super.response, LINKS_PASSWORD_HREF, VndMediaType.PASSWORD_CHANGE, createPasswordChangeJson(oldPassword, newPassword)), this); } } @@ -406,12 +328,18 @@ public class ScmRequests { /** * encapsulate standard assertions over model properties */ - public class ModelResponse { + public class ModelResponse, PREV extends ModelResponse> { + protected PREV previousResponse; protected Response response; - public ModelResponse(Response response) { + public ModelResponse(Response response, PREV previousResponse) { this.response = response; + this.previousResponse = previousResponse; + } + + public PREV returnToPrevious() { + return previousResponse; } public SELF assertSingleProperty(Consumer assertSingleProperty, String propertyJsonPath) { @@ -435,25 +363,45 @@ public class ScmRequests { assertProperties.accept(properties); return (SELF) this; } + + /** + * special assertion of the status code + * + * @param expectedStatusCode the expected status code + * @return the self object + */ + public SELF assertStatusCode(int expectedStatusCode) { + this.response.then().assertThat().statusCode(expectedStatusCode); + return (SELF) this; + } } - public class AppliedChangePasswordRequest extends AppliedRequest { + public class AutoCompleteResponse extends ModelResponse, PREV> { - public AppliedChangePasswordRequest(Response response) { - super(response); + public AutoCompleteResponse(Response response, PREV previousResponse) { + super(response, previousResponse); + } + + public AutoCompleteResponse assertAutoCompleteResults(Consumer> checker) { + List result = response.then().extract().path(""); + checker.accept(result); + return this; } } - public class AppliedUserRequest extends AppliedRequest { - public AppliedUserRequest(Response response) { - super(response); + public class DiffResponse extends ModelResponse, PREV> { + + public DiffResponse(Response response, PREV previousResponse) { + super(response, previousResponse); } + } - public UserResponse usingUserResponse() { - return new UserResponse(super.response); + public class ChangePasswordResponse extends ModelResponse, PREV> { + + public ChangePasswordResponse(Response response, PREV previousResponse) { + super(response, previousResponse); } - } } diff --git a/scm-it/src/test/java/sonia/scm/it/utils/TestData.java b/scm-it/src/test/java/sonia/scm/it/utils/TestData.java index 03da80ea3b..a164fc6649 100644 --- a/scm-it/src/test/java/sonia/scm/it/utils/TestData.java +++ b/scm-it/src/test/java/sonia/scm/it/utils/TestData.java @@ -46,11 +46,11 @@ public class TestData { return DEFAULT_REPOSITORIES.get(repositoryType); } - public static void createUser(String username, String password) { - createUser(username, password, false, "xml"); + public static void createNotAdminUser(String username, String password) { + createUser(username, password, false, "xml", "user1@scm-manager.org"); } - public static void createUser(String username, String password, boolean isAdmin, String type) { + public static void createUser(String username, String password, boolean isAdmin, String type, final String email) { LOG.info("create user with username: {}", username); String admin = isAdmin ? "true" : "false"; given(VndMediaType.USER) @@ -61,7 +61,7 @@ public class TestData { .append(" \"admin\": ").append(admin).append(",\n") .append(" \"creationDate\": \"2018-08-21T12:26:46.084Z\",\n") .append(" \"displayName\": \"").append(username).append("\",\n") - .append(" \"mail\": \"user1@scm-manager.org\",\n") + .append(" \"mail\": \"" + email + "\",\n") .append(" \"name\": \"").append(username).append("\",\n") .append(" \"password\": \"").append(password).append("\",\n") .append(" \"type\": \"").append(type).append("\"\n") @@ -71,6 +71,16 @@ public class TestData { .statusCode(HttpStatus.SC_CREATED) ; } + public static void createGroup(String groupName, String desc) { + LOG.info("create group with group name: {} and description {}", groupName, desc); + given(VndMediaType.GROUP) + .when() + .content(getGroupJson(groupName,desc)) + .post(getGroupsUrl()) + .then() + .statusCode(HttpStatus.SC_CREATED) + ; + } public static void createUserPermission(String name, PermissionType permissionType, String repositoryType) { String defaultPermissionUrl = TestData.getDefaultPermissionUrl(USER_SCM_ADMIN, USER_SCM_ADMIN, repositoryType); @@ -193,28 +203,31 @@ public class TestData { return JSON_BUILDER .add("contact", "zaphod.beeblebrox@hitchhiker.com") .add("description", "Heart of Gold") - .add("name", "HeartOfGold-" + repositoryType) + .add("name", getDefaultRepoName(repositoryType)) .add("archived", false) .add("type", repositoryType) .build().toString(); } - public static URI getMeUrl() { - return RestUtil.createResourceUrl("me/"); + public static String getDefaultRepoName(String repositoryType) { + return "HeartOfGold-" + repositoryType; + } + public static String getGroupJson(String groupname , String desc) { + return JSON_BUILDER + .add("name", groupname) + .add("description", desc) + .build().toString(); + } + + public static URI getGroupsUrl() { + return RestUtil.createResourceUrl("groups/"); } public static URI getUsersUrl() { return RestUtil.createResourceUrl("users/"); - } - public static URI getUserUrl(String username) { - return getUsersUrl().resolve(username); - - } - - public static String createPasswordChangeJson(String oldPassword, String newPassword) { return JSON_BUILDER .add("oldPassword", oldPassword) @@ -225,4 +238,5 @@ public class TestData { public static void main(String[] args) { cleanup(); } + } diff --git a/scm-ui/src/repos/permissions/containers/Permissions.js b/scm-ui/src/repos/permissions/containers/Permissions.js index d29359ed0b..e70e02f443 100644 --- a/scm-ui/src/repos/permissions/containers/Permissions.js +++ b/scm-ui/src/repos/permissions/containers/Permissions.js @@ -121,6 +121,7 @@ class Permissions extends React.Component { {t("permission.group-permission")} {t("permission.type")} + diff --git a/scm-ui/yarn.lock b/scm-ui/yarn.lock index f99004ce97..60c74f0c41 100644 --- a/scm-ui/yarn.lock +++ b/scm-ui/yarn.lock @@ -1979,7 +1979,7 @@ concat-stream@^1.6.0, concat-stream@^1.6.1, concat-stream@~1.6.0: readable-stream "^2.2.2" typedarray "^0.0.6" -connect-history-api-fallback@^1, connect-history-api-fallback@^1.5.0: +connect-history-api-fallback@^1: version "1.5.0" resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz#b06873934bc5e344fef611a196a6faae0aee015a" diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AutoCompleteResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AutoCompleteResource.java index 70192973cb..005e29e9ea 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AutoCompleteResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AutoCompleteResource.java @@ -14,8 +14,8 @@ import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; -import javax.ws.rs.core.Response; import java.util.Collection; +import java.util.List; import java.util.stream.Collectors; @@ -41,7 +41,7 @@ public class AutoCompleteResource { } @GET - @Path("user") + @Path("users") @Produces(VndMediaType.AUTOCOMPLETE) @StatusCodes({ @ResponseCode(code = 200, condition = "success"), @@ -50,12 +50,12 @@ public class AutoCompleteResource { @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"user:autocomplete\" privilege"), @ResponseCode(code = 500, condition = "internal server error") }) - public Response searchUser(@NotEmpty(message = PARAMETER_IS_REQUIRED) @Size(min = MIN_SEARCHED_CHARS, message = INVALID_PARAMETER_LENGTH) @QueryParam("filter") String filter) { + public List searchUser(@NotEmpty(message = PARAMETER_IS_REQUIRED) @Size(min = MIN_SEARCHED_CHARS, message = INVALID_PARAMETER_LENGTH) @QueryParam("q") String filter) { return map(userManager.autocomplete(filter)); } @GET - @Path("group") + @Path("groups") @Produces(VndMediaType.AUTOCOMPLETE) @StatusCodes({ @ResponseCode(code = 200, condition = "success"), @@ -64,16 +64,15 @@ public class AutoCompleteResource { @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"group:autocomplete\" privilege"), @ResponseCode(code = 500, condition = "internal server error") }) - public Response searchGroup(@NotEmpty(message = PARAMETER_IS_REQUIRED) @Size(min = MIN_SEARCHED_CHARS, message = INVALID_PARAMETER_LENGTH) @QueryParam("filter") String filter) { + public List searchGroup(@NotEmpty(message = PARAMETER_IS_REQUIRED) @Size(min = MIN_SEARCHED_CHARS, message = INVALID_PARAMETER_LENGTH) @QueryParam("q") String filter) { return map(groupManager.autocomplete(filter)); } - private Response map(Collection autocomplete) { - return Response.ok(autocomplete + private List map(Collection autocomplete) { + return autocomplete .stream() .map(mapper::map) - .collect(Collectors.toList())) - .build(); + .collect(Collectors.toList()); } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java index c8159f072c..da4368d9b9 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java @@ -1,5 +1,7 @@ package sonia.scm.api.v2.resources; +import com.google.common.collect.Lists; +import de.otto.edison.hal.Link; import de.otto.edison.hal.Links; import org.apache.shiro.SecurityUtils; import sonia.scm.SCMContextProvider; @@ -8,6 +10,7 @@ import sonia.scm.group.GroupPermissions; import sonia.scm.user.UserPermissions; import javax.inject.Inject; +import java.util.List; import static de.otto.edison.hal.Link.link; @@ -24,6 +27,7 @@ public class IndexDtoGenerator { public IndexDto generate() { Links.Builder builder = Links.linkingTo(); + List autoCompleteLinks = Lists.newArrayList(); builder.self(resourceLinks.index().self()); builder.single(link("uiPlugins", resourceLinks.uiPluginCollection().self())); if (SecurityUtils.getSubject().isAuthenticated()) { @@ -34,6 +38,13 @@ public class IndexDtoGenerator { if (UserPermissions.list().isPermitted()) { builder.single(link("users", resourceLinks.userCollection().self())); } + if (UserPermissions.autocomplete().isPermitted()) { + autoCompleteLinks.add(Link.linkBuilder("autocomplete", resourceLinks.autoComplete().users()).withName("users").build()); + } + if (GroupPermissions.autocomplete().isPermitted()) { + autoCompleteLinks.add(Link.linkBuilder("autocomplete", resourceLinks.autoComplete().groups()).withName("groups").build()); + } + builder.array(autoCompleteLinks); if (GroupPermissions.list().isPermitted()) { builder.single(link("groups", resourceLinks.groupCollection().self())); } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java index 488577618c..820e722814 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java @@ -142,6 +142,26 @@ class ResourceLinks { } } + AutoCompleteLinks autoComplete() { + return new AutoCompleteLinks (scmPathInfoStore.get()); + } + + static class AutoCompleteLinks { + private final LinkBuilder linkBuilder; + + AutoCompleteLinks (ScmPathInfo pathInfo) { + linkBuilder = new LinkBuilder(pathInfo, AutoCompleteResource.class); + } + + String users() { + return linkBuilder.method("searchUser").parameters().href(); + } + + String groups() { + return linkBuilder.method("searchGroup").parameters().href(); + } + } + ConfigLinks config() { return new ConfigLinks(scmPathInfoStore.get()); } diff --git a/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java b/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java index 327415ec22..655e0001ce 100644 --- a/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java +++ b/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java @@ -245,7 +245,8 @@ public class DefaultGroupManager extends AbstractGroupManager @Override public Collection autocomplete(String filter) { GroupPermissions.autocomplete().check(); - return search(new SearchRequest(filter,true, DEFAULT_LIMIT)); + SearchRequest searchRequest = new SearchRequest(filter, true, DEFAULT_LIMIT); + return SearchUtil.search(searchRequest, groupDAO.getAll(), group -> matches(searchRequest,group)?group:null); } /** diff --git a/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java b/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java index 19802902b8..21cd4cc319 100644 --- a/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java +++ b/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java @@ -229,6 +229,13 @@ public class DefaultUserManager extends AbstractUserManager fresh.copyProperties(user); } + @Override + public Collection autocomplete(String filter) { + UserPermissions.autocomplete().check(); + SearchRequest searchRequest = new SearchRequest(filter, true, DEFAULT_LIMIT); + return SearchUtil.search(searchRequest, userDAO.getAll(), user -> matches(searchRequest,user)?user:null); + } + /** * Method description * @@ -258,7 +265,7 @@ public class DefaultUserManager extends AbstractUserManager } }); } - + private boolean matches(SearchRequest searchRequest, User user) { return SearchUtil.matchesOne(searchRequest, user.getName(), user.getDisplayName(), user.getMail()); } @@ -277,7 +284,7 @@ public class DefaultUserManager extends AbstractUserManager public User get(String id) { UserPermissions.read().check(id); - + User user = userDAO.get(id); if (user != null) @@ -300,12 +307,6 @@ public class DefaultUserManager extends AbstractUserManager return getAll(null); } - @Override - public Collection autocomplete(String filter) { - UserPermissions.autocomplete().check(); - return search(new SearchRequest(filter,true, DEFAULT_LIMIT)); - } - /** * Method description * diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AutoCompleteResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AutoCompleteResourceTest.java index adc396458c..2cab41bfbf 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AutoCompleteResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AutoCompleteResourceTest.java @@ -1,16 +1,16 @@ package sonia.scm.api.v2.resources; import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.shiro.subject.Subject; -import org.apache.shiro.subject.support.SubjectThreadState; +import com.github.sdorra.shiro.ShiroRule; +import com.github.sdorra.shiro.SubjectAware; 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.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; @@ -43,16 +43,17 @@ import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher; +@SubjectAware(configuration = "classpath:sonia/scm/shiro-002.ini") @RunWith(MockitoJUnitRunner.Silent.class) public class AutoCompleteResourceTest { + @Rule + public final ShiroRule shiroRule = new ShiroRule(); + public static final String URL = "/" + AutoCompleteResource.PATH; private final Integer defaultLimit = Manager.DEFAULT_LIMIT; private Dispatcher dispatcher; - private final Subject subject = mock(Subject.class); - private final ThreadState subjectThreadState = new SubjectThreadState(subject); - private XmlUserDAO userDao; private XmlGroupDAO groupDao; private XmlDatabase xmlDB; @@ -75,9 +76,6 @@ public class AutoCompleteResourceTest { GroupManager groupManager = new DefaultGroupManager(groupDao); AutoCompleteResource autoCompleteResource = new AutoCompleteResource(mapper, userManager, groupManager); dispatcher = createDispatcher(autoCompleteResource); - subjectThreadState.bind(); - ThreadContext.bind(subject); - when(subject.isPermitted(any(String.class))).thenReturn(true); } @After @@ -85,23 +83,10 @@ public class AutoCompleteResourceTest { ThreadContext.unbindSubject(); } - @Test - public void shouldGet400OnFailedParameterForUserSearch() throws Exception { - MockHttpRequest request = MockHttpRequest - .get("/" + AutoCompleteResource.PATH + "user") - .contentType(VndMediaType.AUTOCOMPLETE) - .accept(VndMediaType.AUTOCOMPLETE); - MockHttpResponse response = new MockHttpResponse(); - - dispatcher.invoke(request, response); - - assertEquals(400, response.getStatus()); - } - @Test public void shouldGet400IfParameterLengthLessThan2CharsForUserSearch() throws Exception { MockHttpRequest request = MockHttpRequest - .get("/" + AutoCompleteResource.PATH + "user?filter=a") + .get("/" + AutoCompleteResource.PATH + "users?q=a") .contentType(VndMediaType.AUTOCOMPLETE) .accept(VndMediaType.AUTOCOMPLETE); MockHttpResponse response = new MockHttpResponse(); @@ -112,12 +97,27 @@ public class AutoCompleteResourceTest { } @Test + @SubjectAware(username = "trillian", password = "secret") + public void shouldGet400OnFailedParameterForUserSearch() throws Exception { + MockHttpRequest request = MockHttpRequest + .get("/" + AutoCompleteResource.PATH + "users") + .contentType(VndMediaType.AUTOCOMPLETE) + .accept(VndMediaType.AUTOCOMPLETE); + MockHttpResponse response = new MockHttpResponse(); + + dispatcher.invoke(request, response); + + assertEquals(400, response.getStatus()); + } + + @Test + @SubjectAware(username = "trillian", password = "secret") public void shouldSearchUsers() throws Exception { ArrayList users = Lists.newArrayList(createMockUser("YuCantFindMe", "ha ha"), createMockUser("user1", "User 1"), createMockUser("user2", "User 2")); String searched = "user"; when(xmlDB.values()).thenReturn(users); MockHttpRequest request = MockHttpRequest - .get("/" + AutoCompleteResource.PATH + "user?filter=" + searched) + .get("/" + AutoCompleteResource.PATH + "users?q=" + searched) .contentType(VndMediaType.AUTOCOMPLETE) .accept(VndMediaType.AUTOCOMPLETE); MockHttpResponse response = new MockHttpResponse(); @@ -134,11 +134,27 @@ public class AutoCompleteResourceTest { } @Test + @SubjectAware(username = "user_without_autocomplete_permission", password = "secret") + public void shouldGet403OnAutoCompleteUsers() throws Exception { + MockHttpRequest request = MockHttpRequest + .get("/" + AutoCompleteResource.PATH + "users?q=user" ) + .contentType(VndMediaType.AUTOCOMPLETE) + .accept(VndMediaType.AUTOCOMPLETE); + MockHttpResponse response = new MockHttpResponse(); + + dispatcher.invoke(request, response); + + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); + } + + + @Test + @SubjectAware(username = "trillian", password = "secret") public void shouldSearchUsersWithDefaultLimitLength() throws Exception { List userList = IntStream.range(0, 10).boxed().map(i -> createMockUser("user" + i, "User " + i)).collect(Collectors.toList()); when(xmlDB.values()).thenReturn(userList); MockHttpRequest request = MockHttpRequest - .get("/" + AutoCompleteResource.PATH + "user?filter=user") + .get("/" + AutoCompleteResource.PATH + "users?q=user") .contentType(VndMediaType.AUTOCOMPLETE) .accept(VndMediaType.AUTOCOMPLETE); MockHttpResponse response = new MockHttpResponse(); @@ -150,9 +166,10 @@ public class AutoCompleteResourceTest { } @Test + @SubjectAware(username = "trillian", password = "secret") public void shouldGet400OnFailedParameterForGroupSearch() throws Exception { MockHttpRequest request = MockHttpRequest - .get("/" + AutoCompleteResource.PATH + "group") + .get("/" + AutoCompleteResource.PATH + "groups") .contentType(VndMediaType.AUTOCOMPLETE) .accept(VndMediaType.AUTOCOMPLETE); MockHttpResponse response = new MockHttpResponse(); @@ -163,9 +180,10 @@ public class AutoCompleteResourceTest { } @Test + @SubjectAware(username = "trillian", password = "secret") public void shouldGet400IfParameterLengthLessThan2CharsForGroupSearch() throws Exception { MockHttpRequest request = MockHttpRequest - .get("/" + AutoCompleteResource.PATH + "group?filter=a") + .get("/" + AutoCompleteResource.PATH + "groups?q=a") .contentType(VndMediaType.AUTOCOMPLETE) .accept(VndMediaType.AUTOCOMPLETE); MockHttpResponse response = new MockHttpResponse(); @@ -176,12 +194,13 @@ public class AutoCompleteResourceTest { } @Test + @SubjectAware(username = "trillian", password = "secret") public void shouldSearchGroups() throws Exception { ArrayList groups = Lists.newArrayList(createMockGroup("YuCantFindMe"), createMockGroup("group_1"), createMockGroup("group_2")); String searched = "group"; when(xmlDB.values()).thenReturn(groups); MockHttpRequest request = MockHttpRequest - .get("/" + AutoCompleteResource.PATH + "group?filter=" + searched) + .get("/" + AutoCompleteResource.PATH + "groups?q=" + searched) .contentType(VndMediaType.AUTOCOMPLETE) .accept(VndMediaType.AUTOCOMPLETE); MockHttpResponse response = new MockHttpResponse(); @@ -197,11 +216,26 @@ public class AutoCompleteResourceTest { } @Test + @SubjectAware(username = "user_without_autocomplete_permission", password = "secret") + public void shouldGet403OnAutoCompleteGroups() throws Exception { + MockHttpRequest request = MockHttpRequest + .get("/" + AutoCompleteResource.PATH + "groups?q=user" ) + .contentType(VndMediaType.AUTOCOMPLETE) + .accept(VndMediaType.AUTOCOMPLETE); + MockHttpResponse response = new MockHttpResponse(); + + dispatcher.invoke(request, response); + + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); + } + + @Test + @SubjectAware(username = "trillian", password = "secret") public void shouldSearchGroupsWithDefaultLimitLength() throws Exception { List groups = IntStream.range(0, 10).boxed().map(i -> createMockGroup("group_" + i)).collect(Collectors.toList()); when(xmlDB.values()).thenReturn(groups); MockHttpRequest request = MockHttpRequest - .get("/" + AutoCompleteResource.PATH + "group?filter=group") + .get("/" + AutoCompleteResource.PATH + "groups?q=group") .contentType(VndMediaType.AUTOCOMPLETE) .accept(VndMediaType.AUTOCOMPLETE); MockHttpResponse response = new MockHttpResponse(); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IndexResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IndexResourceTest.java index 83697a3c4a..93099cf5ea 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IndexResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IndexResourceTest.java @@ -13,7 +13,7 @@ import java.util.Optional; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -@SubjectAware(configuration = "classpath:sonia/scm/shiro-001.ini") +@SubjectAware(configuration = "classpath:sonia/scm/shiro-002.ini") public class IndexResourceTest { @Rule @@ -94,6 +94,26 @@ public class IndexResourceTest { Assertions.assertThat(index.getLinks().getLinkBy("config")).matches(o -> !o.isPresent()); } + @Test + @SubjectAware(username = "trillian", password = "secret") + public void shouldRenderAutoCompleteLinks() { + IndexDto index = indexResource.getIndex(); + + Assertions.assertThat(index.getLinks().getLinksBy("autocomplete")) + .extracting("name") + .containsExactlyInAnyOrder("users", "groups"); + } + + @Test + @SubjectAware(username = "user_without_autocomplete_permission", password = "secret") + public void userWithoutAutocompletePermissionShouldNotSeeAutoCompleteLinks() { + IndexDto index = indexResource.getIndex(); + + Assertions.assertThat(index.getLinks().getLinksBy("autocomplete")) + .extracting("name") + .doesNotContainSequence("users", "groups"); + } + @Test @SubjectAware(username = "dent", password = "secret") public void shouldRenderAdminLinksIfAuthorized() { diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java index eed46a1f6e..c2dc685306 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java @@ -16,6 +16,7 @@ public class ResourceLinksMock { when(resourceLinks.user()).thenReturn(userLinks); when(resourceLinks.me()).thenReturn(new ResourceLinks.MeLinks(uriInfo,userLinks)); when(resourceLinks.userCollection()).thenReturn(new ResourceLinks.UserCollectionLinks(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.repository()).thenReturn(new ResourceLinks.RepositoryLinks(uriInfo)); diff --git a/scm-webapp/src/test/resources/sonia/scm/shiro-002.ini b/scm-webapp/src/test/resources/sonia/scm/shiro-002.ini new file mode 100644 index 0000000000..58ad2aa813 --- /dev/null +++ b/scm-webapp/src/test/resources/sonia/scm/shiro-002.ini @@ -0,0 +1,9 @@ +[users] +user_without_autocomplete_permission = secret +trillian = secret, user_ac, group_ac +dent = secret, admin + +[roles] +admin = * +user_ac = user:autocomplete +group_ac = group:autocomplete