JAMES-2138 Implement address/groups endpoints using RRT
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/b4218540 Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/b4218540 Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/b4218540 Branch: refs/heads/master Commit: b4218540785c8164f0a0555cbb592e244bd0cf5f Parents: 936746b Author: Matthieu Baechler <matth...@apache.org> Authored: Tue Sep 5 17:51:03 2017 +0200 Committer: benwa <btell...@linagora.com> Committed: Sat Sep 9 10:46:02 2017 +0700 ---------------------------------------------------------------------- .../james/modules/server/DataRoutesModules.java | 2 + .../java/org/apache/james/rrt/lib/Mapping.java | 2 + .../org/apache/james/rrt/lib/MappingImpl.java | 15 +- .../apache/james/rrt/lib/MappingImplTest.java | 21 + .../WebAdminServerIntegrationTest.java | 4 +- .../org/apache/james/webadmin/Constants.java | 1 + .../james/webadmin/routes/GroupsRoutes.java | 214 ++++++++ .../james/webadmin/routes/GroupsRoutesTest.java | 515 +++++++++++++++++++ 8 files changed, 766 insertions(+), 8 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/b4218540/server/container/guice/protocols/webadmin-data/src/main/java/org/apache/james/modules/server/DataRoutesModules.java ---------------------------------------------------------------------- diff --git a/server/container/guice/protocols/webadmin-data/src/main/java/org/apache/james/modules/server/DataRoutesModules.java b/server/container/guice/protocols/webadmin-data/src/main/java/org/apache/james/modules/server/DataRoutesModules.java index 48233d7..a279651 100644 --- a/server/container/guice/protocols/webadmin-data/src/main/java/org/apache/james/modules/server/DataRoutesModules.java +++ b/server/container/guice/protocols/webadmin-data/src/main/java/org/apache/james/modules/server/DataRoutesModules.java @@ -21,6 +21,7 @@ package org.apache.james.modules.server; import org.apache.james.webadmin.Routes; import org.apache.james.webadmin.routes.DomainsRoutes; +import org.apache.james.webadmin.routes.GroupsRoutes; import org.apache.james.webadmin.routes.UserRoutes; import com.google.inject.AbstractModule; @@ -32,6 +33,7 @@ public class DataRoutesModules extends AbstractModule { protected void configure() { Multibinder<Routes> routesMultibinder = Multibinder.newSetBinder(binder(), Routes.class); routesMultibinder.addBinding().to(DomainsRoutes.class); + routesMultibinder.addBinding().to(GroupsRoutes.class); routesMultibinder.addBinding().to(UserRoutes.class); } } http://git-wip-us.apache.org/repos/asf/james-project/blob/b4218540/server/data/data-api/src/main/java/org/apache/james/rrt/lib/Mapping.java ---------------------------------------------------------------------- diff --git a/server/data/data-api/src/main/java/org/apache/james/rrt/lib/Mapping.java b/server/data/data-api/src/main/java/org/apache/james/rrt/lib/Mapping.java index e9ef3b2..2354d22 100644 --- a/server/data/data-api/src/main/java/org/apache/james/rrt/lib/Mapping.java +++ b/server/data/data-api/src/main/java/org/apache/james/rrt/lib/Mapping.java @@ -23,6 +23,8 @@ package org.apache.james.rrt.lib; public interface Mapping { + String getAddress(); + enum Type { Regex, Domain, Error, Address } Type getType(); http://git-wip-us.apache.org/repos/asf/james-project/blob/b4218540/server/data/data-library/src/main/java/org/apache/james/rrt/lib/MappingImpl.java ---------------------------------------------------------------------- diff --git a/server/data/data-library/src/main/java/org/apache/james/rrt/lib/MappingImpl.java b/server/data/data-library/src/main/java/org/apache/james/rrt/lib/MappingImpl.java index 4f0c97a..a17df46 100644 --- a/server/data/data-library/src/main/java/org/apache/james/rrt/lib/MappingImpl.java +++ b/server/data/data-library/src/main/java/org/apache/james/rrt/lib/MappingImpl.java @@ -92,10 +92,16 @@ public class MappingImpl implements Mapping, Serializable { @Override public String getErrorMessage() { - Preconditions.checkState(mapping.startsWith(RecipientRewriteTable.ERROR_PREFIX)); + Preconditions.checkState(getType() == Type.Error); return mapping.substring(RecipientRewriteTable.ERROR_PREFIX.length()); } - + + @Override + public String getAddress() { + Preconditions.checkState(getType() == Type.Address); + return mapping; + } + @Override public boolean equals(Object other) { if (other instanceof MappingImpl) { @@ -104,15 +110,14 @@ public class MappingImpl implements Mapping, Serializable { } return false; } - + @Override public int hashCode() { return Objects.hashCode(mapping); } - + @Override public String toString() { return "MappingImpl{mapping=" + mapping + "}"; } - } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/james-project/blob/b4218540/server/data/data-library/src/test/java/org/apache/james/rrt/lib/MappingImplTest.java ---------------------------------------------------------------------- diff --git a/server/data/data-library/src/test/java/org/apache/james/rrt/lib/MappingImplTest.java b/server/data/data-library/src/test/java/org/apache/james/rrt/lib/MappingImplTest.java index 5844fdc..c4ccce6 100644 --- a/server/data/data-library/src/test/java/org/apache/james/rrt/lib/MappingImplTest.java +++ b/server/data/data-library/src/test/java/org/apache/james/rrt/lib/MappingImplTest.java @@ -21,6 +21,7 @@ package org.apache.james.rrt.lib; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import org.junit.Test; @@ -132,4 +133,24 @@ public class MappingImplTest { public void toStringShouldReturnValuePrefixedAsByMoreObject() { assertThat(MappingImpl.of("value").toString()).isEqualTo("MappingImpl{mapping=value}"); } + + @Test + public void getAddressShouldReturnMappingValueForAddress() { + assertThat(MappingImpl.address("value").getAddress()).isEqualTo("value"); + } + + @Test + public void getAddressShouldThrowForError() { + assertThatThrownBy(() -> MappingImpl.error("value").getAddress()).isInstanceOf(IllegalStateException.class); + } + + @Test + public void getAddressShouldThrowForRegex() { + assertThatThrownBy(() -> MappingImpl.regex("value").getAddress()).isInstanceOf(IllegalStateException.class); + } + + @Test + public void getAddressShouldThrowForDomain() { + assertThatThrownBy(() -> MappingImpl.domain("value").getAddress()).isInstanceOf(IllegalStateException.class); + } } http://git-wip-us.apache.org/repos/asf/james-project/blob/b4218540/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/WebAdminServerIntegrationTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/WebAdminServerIntegrationTest.java b/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/WebAdminServerIntegrationTest.java index 919ac42..6835242 100644 --- a/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/WebAdminServerIntegrationTest.java +++ b/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/WebAdminServerIntegrationTest.java @@ -268,9 +268,7 @@ public class WebAdminServerIntegrationTest { .body(containsString("\"tags\":[\"GlobalQuota\"]")) .body(containsString("\"tags\":[\"Domains\"]")) .body(containsString("\"tags\":[\"Users\"]")) - ; + .body(containsString("\"tags\":[\"Address Groups\"]")); } - //TODO: check Groups full path - } http://git-wip-us.apache.org/repos/asf/james-project/blob/b4218540/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/Constants.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/Constants.java b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/Constants.java index 1031a86..70cfbbb 100644 --- a/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/Constants.java +++ b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/Constants.java @@ -23,5 +23,6 @@ public interface Constants { String SEPARATOR = "/"; String EMPTY_BODY = ""; + String JSON_CONTENT_TYPE = "application/json"; } http://git-wip-us.apache.org/repos/asf/james-project/blob/b4218540/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/GroupsRoutes.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/GroupsRoutes.java b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/GroupsRoutes.java new file mode 100644 index 0000000..f47fda1 --- /dev/null +++ b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/GroupsRoutes.java @@ -0,0 +1,214 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.webadmin.routes; + +import static org.apache.james.webadmin.Constants.SEPARATOR; +import static spark.Spark.halt; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import javax.inject.Inject; +import javax.mail.internet.AddressException; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; + +import org.apache.james.core.MailAddress; +import org.apache.james.domainlist.api.DomainList; +import org.apache.james.domainlist.api.DomainListException; +import org.apache.james.rrt.api.RecipientRewriteTable; +import org.apache.james.rrt.api.RecipientRewriteTableException; +import org.apache.james.rrt.lib.Mapping; +import org.apache.james.rrt.lib.Mappings; +import org.apache.james.user.api.UsersRepository; +import org.apache.james.user.api.UsersRepositoryException; +import org.apache.james.util.streams.Iterators; +import org.apache.james.webadmin.Constants; +import org.apache.james.webadmin.Routes; +import org.apache.james.webadmin.utils.JsonExtractException; +import org.apache.james.webadmin.utils.JsonTransformer; +import org.eclipse.jetty.http.HttpStatus; + +import com.github.steveash.guavate.Guavate; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableSortedSet; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; +import spark.HaltException; +import spark.Request; +import spark.Response; +import spark.Service; + +@Api(tags = "Address Groups") +@Path(GroupsRoutes.ROOT_PATH) +@Produces(Constants.JSON_CONTENT_TYPE) +public class GroupsRoutes implements Routes { + + public static final String ROOT_PATH = "address/groups"; + private static final String GROUP_ADDRESS = "groupAddress"; + private static final String GROUP_ADDRESS_PATH = ROOT_PATH + SEPARATOR + ":" + GROUP_ADDRESS; + private static final String USER_ADDRESS = "userAddress"; + private static final String USER_IN_GROUP_ADDRESS_PATH = GROUP_ADDRESS_PATH + SEPARATOR + ":" + USER_ADDRESS; + + private final UsersRepository usersRepository; + private final DomainList domainList; + private final JsonTransformer jsonTransformer; + private final RecipientRewriteTable recipientRewriteTable; + + @Inject + @VisibleForTesting + GroupsRoutes(RecipientRewriteTable recipientRewriteTable, UsersRepository usersRepository, + DomainList domainList, JsonTransformer jsonTransformer) { + this.usersRepository = usersRepository; + this.domainList = domainList; + this.jsonTransformer = jsonTransformer; + this.recipientRewriteTable = recipientRewriteTable; + } + + @Override + public void define(Service service) { + service.get(ROOT_PATH, this::listGroups, jsonTransformer); + service.get(GROUP_ADDRESS_PATH, this::listGroupMembers, jsonTransformer); + service.put(GROUP_ADDRESS_PATH, (request, response) -> halt(HttpStatus.BAD_REQUEST_400)); + service.put(USER_IN_GROUP_ADDRESS_PATH, this::addToGroup); + service.delete(GROUP_ADDRESS_PATH, (request, response) -> halt(HttpStatus.BAD_REQUEST_400)); + service.delete(USER_IN_GROUP_ADDRESS_PATH, this::removeFromGroup); + } + + @GET + @Path(ROOT_PATH) + @ApiOperation(value = "getting groups list") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "OK", response = List.class), + @ApiResponse(code = 500, message = "Internal server error - Something went bad on the server side.") + }) + public Set<String> listGroups(Request request, Response response) throws RecipientRewriteTableException { + return Optional.ofNullable(recipientRewriteTable.getAllMappings()) + .map(mappings -> + mappings.entrySet().stream() + .filter(e -> e.getValue().contains(Mapping.Type.Address)) + .map(Map.Entry::getKey) + .collect(Guavate.toImmutableSortedSet())) + .orElse(ImmutableSortedSet.of()); + } + + @PUT + @Path(ROOT_PATH + "/{" + GROUP_ADDRESS + "}/{" + USER_ADDRESS + "}") + @ApiOperation(value = "adding a member into a group") + @ApiImplicitParams({ + @ApiImplicitParam(required = true, dataType = "string", name = GROUP_ADDRESS, paramType = "path"), + @ApiImplicitParam(required = true, dataType = "string", name = USER_ADDRESS, paramType = "path") + }) + @ApiResponses(value = { + @ApiResponse(code = 200, message = "OK", response = List.class), + @ApiResponse(code = 400, message = GROUP_ADDRESS + " or group structure format is not valid"), + @ApiResponse(code = 403, message = "server doesn't own the domain"), + @ApiResponse(code = 409, message = "requested group address is already used for another purpose"), + @ApiResponse(code = 500, message = "Internal server error - Something went bad on the server side.") + }) + public HaltException addToGroup(Request request, Response response) throws JsonExtractException, AddressException, RecipientRewriteTableException, UsersRepositoryException, DomainListException { + MailAddress groupAddress = parseMailAddress(request.params(GROUP_ADDRESS)); + ensureRegisteredDomain(groupAddress.getDomain()); + ensureNotShadowingAnotherAddress(groupAddress); + MailAddress userAddress = parseMailAddress(request.params(USER_ADDRESS)); + recipientRewriteTable.addAddressMapping(groupAddress.getLocalPart(), groupAddress.getDomain(), userAddress.asString()); + return halt(HttpStatus.CREATED_201); + } + + private void ensureRegisteredDomain(String domain) throws DomainListException { + if (!domainList.containsDomain(domain)) { + throw halt(HttpStatus.FORBIDDEN_403); + } + } + + private void ensureNotShadowingAnotherAddress(MailAddress groupAddress) throws UsersRepositoryException { + if (usersRepository.contains(groupAddress.asString())) { + throw halt(HttpStatus.CONFLICT_409); + } + } + + + @DELETE + @Path(ROOT_PATH + "/{" + GROUP_ADDRESS + "}/{" + USER_ADDRESS + "}") + @ApiOperation(value = "remove a member from a group") + @ApiImplicitParams({ + @ApiImplicitParam(required = true, dataType = "string", name = GROUP_ADDRESS, paramType = "path"), + @ApiImplicitParam(required = true, dataType = "string", name = USER_ADDRESS, paramType = "path") + }) + @ApiResponses(value = { + @ApiResponse(code = 200, message = "OK", response = List.class), + @ApiResponse(code = 400, message = GROUP_ADDRESS + " or group structure format is not valid"), + @ApiResponse(code = 500, message = "Internal server error - Something went bad on the server side.") + }) + public HaltException removeFromGroup(Request request, Response response) throws JsonExtractException, AddressException, RecipientRewriteTableException { + MailAddress groupAddress = parseMailAddress(request.params(GROUP_ADDRESS)); + MailAddress userAddress = parseMailAddress(request.params(USER_ADDRESS)); + recipientRewriteTable.removeAddressMapping(groupAddress.getLocalPart(), groupAddress.getDomain(), userAddress.asString()); + return halt(HttpStatus.OK_200); + } + + @GET + @Path(ROOT_PATH + "/{" + GROUP_ADDRESS + "}") + @ApiOperation(value = "listing group members") + @ApiImplicitParams({ + @ApiImplicitParam(required = true, dataType = "string", name = GROUP_ADDRESS, paramType = "path") + }) + @ApiResponses(value = { + @ApiResponse(code = 200, message = "OK", response = List.class), + @ApiResponse(code = 400, message = "The group is not an address"), + @ApiResponse(code = 404, message = "The group does not exist"), + @ApiResponse(code = 500, message = "Internal server error - Something went bad on the server side.") + }) + public ImmutableSortedSet<String> listGroupMembers(Request request, Response response) throws RecipientRewriteTable.ErrorMappingException, RecipientRewriteTableException { + MailAddress groupAddress = parseMailAddress(request.params(GROUP_ADDRESS)); + Mappings mappings = recipientRewriteTable.getMappings(groupAddress.getLocalPart(), groupAddress.getDomain()); + + ensureNonEmptyMappings(mappings); + + return Iterators + .toStream(mappings.select(Mapping.Type.Address).iterator()) + .map(Mapping::getAddress) + .collect(Guavate.toImmutableSortedSet()); + } + + private MailAddress parseMailAddress(String address) { + try { + return new MailAddress(address); + } catch (AddressException e) { + throw halt(HttpStatus.BAD_REQUEST_400); + } + } + + private void ensureNonEmptyMappings(Mappings mappings) { + if (mappings == null || mappings.isEmpty()) { + throw halt(HttpStatus.NOT_FOUND_404); + } + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/b4218540/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/GroupsRoutesTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/GroupsRoutesTest.java b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/GroupsRoutesTest.java new file mode 100644 index 0000000..f866f6a --- /dev/null +++ b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/GroupsRoutesTest.java @@ -0,0 +1,515 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.webadmin.routes; + +import static com.jayway.restassured.RestAssured.given; +import static com.jayway.restassured.RestAssured.when; +import static com.jayway.restassured.config.EncoderConfig.encoderConfig; +import static com.jayway.restassured.config.RestAssuredConfig.newConfig; +import static org.apache.james.webadmin.Constants.SEPARATOR; +import static org.apache.james.webadmin.WebAdminServer.NO_CONFIGURATION; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.CoreMatchers.is; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +import org.apache.james.dnsservice.api.DNSService; +import org.apache.james.domainlist.api.DomainList; +import org.apache.james.domainlist.api.DomainListException; +import org.apache.james.domainlist.memory.MemoryDomainList; +import org.apache.james.metrics.logger.DefaultMetricFactory; +import org.apache.james.rrt.api.RecipientRewriteTable; +import org.apache.james.rrt.api.RecipientRewriteTableException; +import org.apache.james.rrt.memory.MemoryRecipientRewriteTable; +import org.apache.james.user.api.UsersRepository; +import org.apache.james.user.api.UsersRepositoryException; +import org.apache.james.user.memory.MemoryUsersRepository; +import org.apache.james.webadmin.WebAdminServer; +import org.apache.james.webadmin.utils.JsonTransformer; +import org.eclipse.jetty.http.HttpStatus; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; + +import com.jayway.restassured.RestAssured; +import com.jayway.restassured.builder.RequestSpecBuilder; +import com.jayway.restassured.filter.log.LogDetail; +import com.jayway.restassured.http.ContentType; +import de.bechte.junit.runners.context.HierarchicalContextRunner; + +@RunWith(HierarchicalContextRunner.class) +public class GroupsRoutesTest { + + private static final String DOMAIN = "b.com"; + private static final String GROUP1 = "group1" + "@" + DOMAIN; + private static final String GROUP2 = "group2" + "@" + DOMAIN; + private static final String USER_A = "a" + "@" + DOMAIN; + private static final String USER_B = "b" + "@" + DOMAIN; + + private WebAdminServer webAdminServer; + + private void createServer(GroupsRoutes groupsRoutes) throws Exception { + webAdminServer = new WebAdminServer( + new DefaultMetricFactory(), + groupsRoutes); + webAdminServer.configure(NO_CONFIGURATION); + webAdminServer.await(); + + RestAssured.requestSpecification = new RequestSpecBuilder() + .setContentType(ContentType.JSON) + .setAccept(ContentType.JSON) + .setConfig(newConfig().encoderConfig(encoderConfig().defaultContentCharset(StandardCharsets.UTF_8))) + .setPort(webAdminServer.getPort().toInt()) + .setBasePath(GroupsRoutes.ROOT_PATH) + .log(LogDetail.ALL) + .build(); + } + + @After + public void stop() { + webAdminServer.destroy(); + } + + public class NormalBehaviour { + + MemoryUsersRepository usersRepository; + MemoryDomainList domainList; + MemoryRecipientRewriteTable memoryRecipientRewriteTable; + + @Before + public void setUp() throws Exception { + memoryRecipientRewriteTable = new MemoryRecipientRewriteTable(); + DNSService dnsService = mock(DNSService.class); + domainList = new MemoryDomainList(dnsService); + domainList.addDomain(DOMAIN); + usersRepository = MemoryUsersRepository.withVirtualHosting(); + usersRepository.setDomainList(domainList); + createServer(new GroupsRoutes(memoryRecipientRewriteTable, usersRepository, domainList, new JsonTransformer())); + } + + @Test + public void getGroupsShouldBeEmpty() { + when() + .get() + .then() + .contentType(ContentType.JSON) + .statusCode(HttpStatus.OK_200) + .body(is("[]")); + } + + @Test + public void getGroupsShouldListExistingGroupsInOrder() { + given() + .put(GROUP2 + SEPARATOR + USER_A); + + given() + .put(GROUP1 + SEPARATOR + USER_A); + + List<String> addresses = + when() + .get() + .then() + .contentType(ContentType.JSON) + .statusCode(HttpStatus.OK_200) + .extract() + .body() + .jsonPath() + .getList("."); + assertThat(addresses).containsExactly(GROUP1, GROUP2); + } + + @Test + public void getUnregisteredGroupShouldReturnNotFound() { + when() + .get("unknown@domain.travel") + .then() + .statusCode(HttpStatus.NOT_FOUND_404); + } + + @Test + public void putUserInGroupShouldReturnCreated() { + when() + .put(GROUP1 + SEPARATOR + USER_A) + .then() + .statusCode(HttpStatus.CREATED_201); + } + + @Test + public void putUserInGroupShouldCreateGroup() { + when() + .put(GROUP1 + SEPARATOR + USER_A); + + List<String> addresses = + when() + .get(GROUP1) + .then() + .contentType(ContentType.JSON) + .statusCode(HttpStatus.OK_200) + .extract() + .body() + .jsonPath() + .getList("."); + assertThat(addresses).containsExactly(USER_A); + } + + @Test + public void putSameUserInGroupTwiceShouldBeIdempotent() { + given() + .put(GROUP1 + SEPARATOR + USER_A); + + when() + .put(GROUP1 + SEPARATOR + USER_A); + + List<String> addresses = + when() + .get(GROUP1) + .then() + .contentType(ContentType.JSON) + .statusCode(HttpStatus.OK_200) + .extract() + .body() + .jsonPath() + .getList("."); + assertThat(addresses).containsExactly(USER_A); + } + + @Test + public void putUserInGroupShouldAllowSeveralUsers() { + given() + .put(GROUP1 + SEPARATOR + USER_A); + + given() + .put(GROUP1 + SEPARATOR + USER_B); + + List<String> addresses = + when() + .get(GROUP1) + .then() + .contentType(ContentType.JSON) + .statusCode(HttpStatus.OK_200) + .extract() + .body() + .jsonPath() + .getList("."); + assertThat(addresses).containsExactly(USER_A, USER_B); + } + + @Test + public void putUserInGroupShouldNotAllowGroupOnUnregisteredDomain() throws UsersRepositoryException, DomainListException { + when() + .put("group@unregisteredDomain" + SEPARATOR + USER_A) + .then() + .contentType(ContentType.JSON) + .statusCode(HttpStatus.FORBIDDEN_403); + } + + + @Test + public void putUserInGroupShouldNotAllowUserShadowing() throws UsersRepositoryException, DomainListException { + usersRepository.addUser(USER_A, "whatever"); + + when() + .put(USER_A + SEPARATOR + USER_B) + .then() + .contentType(ContentType.JSON) + .statusCode(HttpStatus.CONFLICT_409); + } + + @Test + public void getGroupShouldReturnMembersInOrder() { + given() + .put(GROUP1 + SEPARATOR + USER_B); + + given() + .put(GROUP1 + SEPARATOR + USER_A); + + List<String> addresses = + when() + .get(GROUP1) + .then() + .contentType(ContentType.JSON) + .statusCode(HttpStatus.OK_200) + .extract() + .body() + .jsonPath() + .getList("."); + assertThat(addresses).containsExactly(USER_A, USER_B); + } + + + @Test + public void deleteUserNotInGroupShouldReturnOK() { + when() + .delete(GROUP1 + SEPARATOR + USER_A) + .then() + .statusCode(HttpStatus.OK_200); + } + + @Test + public void deleteLastUserInGroupShouldDeleteGroup() { + given() + .put(GROUP1 + SEPARATOR + USER_A); + + given() + .delete(GROUP1 + SEPARATOR + USER_A); + + when() + .get() + .then() + .contentType(ContentType.JSON) + .statusCode(HttpStatus.OK_200) + .body(is("[]")); + } + } + + public class FilteringOtherRewriteRuleTypes extends NormalBehaviour { + + @Before + public void setup() throws Exception { + super.setUp(); + memoryRecipientRewriteTable.addErrorMapping("error", DOMAIN, "disabled"); + memoryRecipientRewriteTable.addRegexMapping("regex", DOMAIN, ".*@b\\.com"); + memoryRecipientRewriteTable.addAliasDomainMapping("alias", DOMAIN); + + } + + } + + public class ExceptionHandling { + + private RecipientRewriteTable memoryRecipientRewriteTable; + + @Before + public void setUp() throws Exception { + memoryRecipientRewriteTable = mock(RecipientRewriteTable.class); + UsersRepository userRepository = mock(UsersRepository.class); + DomainList domainList = mock(DomainList.class); + Mockito.when(domainList.containsDomain(anyString())).thenReturn(true); + createServer(new GroupsRoutes(memoryRecipientRewriteTable, userRepository, domainList, new JsonTransformer())); + } + + @Test + public void getMalformedGroupShouldReturnBadRequest() { + when() + .get("not-an-address") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400); + } + + @Test + public void putMalformedGroupShouldReturnBadRequest() { + when() + .put("not-an-address" + SEPARATOR + USER_A) + .then() + .statusCode(HttpStatus.BAD_REQUEST_400); + } + + @Test + public void putMalformedAddressShouldReturnBadRequest() { + when() + .put(GROUP1 + SEPARATOR + "not-an-address") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400); + } + + @Test + public void putRequiresTwoPathParams() { + when() + .put(GROUP1) + .then() + .statusCode(HttpStatus.BAD_REQUEST_400); + } + + @Test + public void deleteMalformedGroupShouldReturnBadRequest() { + when() + .delete("not-an-address" + SEPARATOR + USER_A) + .then() + .statusCode(HttpStatus.BAD_REQUEST_400); + } + + @Test + public void deleteMalformedAddressShouldReturnBadRequest() { + when() + .delete(GROUP1 + SEPARATOR + "not-an-address") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400); + } + + @Test + public void deleteRequiresTwoPathParams() { + when() + .delete(GROUP1) + .then() + .statusCode(HttpStatus.BAD_REQUEST_400); + } + + @Test + public void putShouldReturnErrorWhenRecipientRewriteTableExceptionIsThrown() throws Exception { + doThrow(RecipientRewriteTableException.class) + .when(memoryRecipientRewriteTable) + .addAddressMapping(anyString(), anyString(), anyString()); + + when() + .put(GROUP1 + SEPARATOR + GROUP2) + .then() + .statusCode(HttpStatus.INTERNAL_SERVER_ERROR_500); + } + + @Test + public void putShouldReturnErrorWhenErrorMappingExceptionIsThrown() throws Exception { + doThrow(RecipientRewriteTable.ErrorMappingException.class) + .when(memoryRecipientRewriteTable) + .addAddressMapping(anyString(), anyString(), anyString()); + + when() + .put(GROUP1 + SEPARATOR + GROUP2) + .then() + .statusCode(HttpStatus.INTERNAL_SERVER_ERROR_500); + } + + @Test + public void putShouldReturnErrorWhenRuntimeExceptionIsThrown() throws Exception { + doThrow(RuntimeException.class) + .when(memoryRecipientRewriteTable) + .addAddressMapping(anyString(), anyString(), anyString()); + + when() + .put(GROUP1 + SEPARATOR + GROUP2) + .then() + .statusCode(HttpStatus.INTERNAL_SERVER_ERROR_500); + } + + @Test + public void getAllShouldReturnErrorWhenRecipientRewriteTableExceptionIsThrown() throws Exception { + doThrow(RecipientRewriteTableException.class) + .when(memoryRecipientRewriteTable) + .getAllMappings(); + + when() + .get() + .then() + .statusCode(HttpStatus.INTERNAL_SERVER_ERROR_500); + } + + @Test + public void getAllShouldReturnErrorWhenErrorMappingExceptionIsThrown() throws Exception { + doThrow(RecipientRewriteTable.ErrorMappingException.class) + .when(memoryRecipientRewriteTable) + .getAllMappings(); + + when() + .get() + .then() + .statusCode(HttpStatus.INTERNAL_SERVER_ERROR_500); + } + + @Test + public void getAllShouldReturnErrorWhenRuntimeExceptionIsThrown() throws Exception { + doThrow(RuntimeException.class) + .when(memoryRecipientRewriteTable) + .getAllMappings(); + + when() + .get() + .then() + .statusCode(HttpStatus.INTERNAL_SERVER_ERROR_500); + } + + @Test + public void deleteShouldReturnErrorWhenRecipientRewriteTableExceptionIsThrown() throws Exception { + doThrow(RecipientRewriteTableException.class) + .when(memoryRecipientRewriteTable) + .removeAddressMapping(anyString(), anyString(), anyString()); + + when() + .delete(GROUP1 + SEPARATOR + GROUP2) + .then() + .statusCode(HttpStatus.INTERNAL_SERVER_ERROR_500); + } + + @Test + public void deleteShouldReturnErrorWhenErrorMappingExceptionIsThrown() throws Exception { + doThrow(RecipientRewriteTable.ErrorMappingException.class) + .when(memoryRecipientRewriteTable) + .removeAddressMapping(anyString(), anyString(), anyString()); + + when() + .delete(GROUP1 + SEPARATOR + GROUP2) + .then() + .statusCode(HttpStatus.INTERNAL_SERVER_ERROR_500); + } + + @Test + public void deleteShouldReturnErrorWhenRuntimeExceptionIsThrown() throws Exception { + doThrow(RuntimeException.class) + .when(memoryRecipientRewriteTable) + .removeAddressMapping(anyString(), anyString(), anyString()); + + when() + .delete(GROUP1 + SEPARATOR + GROUP2) + .then() + .statusCode(HttpStatus.INTERNAL_SERVER_ERROR_500); + } + + @Test + public void getShouldReturnErrorWhenRecipientRewriteTableExceptionIsThrown() throws Exception { + doThrow(RecipientRewriteTableException.class) + .when(memoryRecipientRewriteTable) + .getMappings(anyString(), anyString()); + + when() + .get(GROUP1) + .then() + .statusCode(HttpStatus.INTERNAL_SERVER_ERROR_500); + } + + @Test + public void getShouldReturnErrorWhenErrorMappingExceptionIsThrown() throws Exception { + doThrow(RecipientRewriteTable.ErrorMappingException.class) + .when(memoryRecipientRewriteTable) + .getMappings(anyString(), anyString()); + + when() + .get(GROUP1) + .then() + .statusCode(HttpStatus.INTERNAL_SERVER_ERROR_500); + } + + @Test + public void getShouldReturnErrorWhenRuntimeExceptionIsThrown() throws Exception { + doThrow(RuntimeException.class) + .when(memoryRecipientRewriteTable) + .getMappings(anyString(), anyString()); + + when() + .get(GROUP1) + .then() + .statusCode(HttpStatus.INTERNAL_SERVER_ERROR_500); + } + } + +} --------------------------------------------------------------------- To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org For additional commands, e-mail: server-dev-h...@james.apache.org