You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by ma...@apache.org on 2018/06/21 13:57:23 UTC

james-project git commit: JAMES-2149 Create domain mappings via webadmin

Repository: james-project
Updated Branches:
  refs/heads/master d926a617a -> dc59e58bf


JAMES-2149 Create domain mappings via webadmin


Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/dc59e58b
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/dc59e58b
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/dc59e58b

Branch: refs/heads/master
Commit: dc59e58bf0ddc6c50ca0259bff19b90864613aba
Parents: d926a61
Author: Edgar Asatryan <ns...@gmail.com>
Authored: Sat Jun 9 16:15:02 2018 +0400
Committer: Matthieu Baechler <ma...@apache.org>
Committed: Thu Jun 21 15:56:18 2018 +0200

----------------------------------------------------------------------
 .../james/modules/server/DataRoutesModules.java |   2 +
 .../org/apache/james/rrt/lib/MappingsImpl.java  |   7 +-
 server/protocols/webadmin/webadmin-data/pom.xml |   6 +-
 .../webadmin/routes/DomainMappingsRoutes.java   | 206 ++++++++++
 .../routes/DomainMappingsRoutesTest.java        | 386 +++++++++++++++++++
 5 files changed, 605 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/dc59e58b/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 2ae64ec..ef695fa 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
@@ -20,6 +20,7 @@
 package org.apache.james.modules.server;
 
 import org.apache.james.webadmin.Routes;
+import org.apache.james.webadmin.routes.DomainMappingsRoutes;
 import org.apache.james.webadmin.routes.DomainsRoutes;
 import org.apache.james.webadmin.routes.ForwardRoutes;
 import org.apache.james.webadmin.routes.GroupsRoutes;
@@ -34,6 +35,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(DomainMappingsRoutes.class);
         routesMultibinder.addBinding().to(ForwardRoutes.class);
         routesMultibinder.addBinding().to(GroupsRoutes.class);
         routesMultibinder.addBinding().to(UserRoutes.class);

http://git-wip-us.apache.org/repos/asf/james-project/blob/dc59e58b/server/data/data-library/src/main/java/org/apache/james/rrt/lib/MappingsImpl.java
----------------------------------------------------------------------
diff --git a/server/data/data-library/src/main/java/org/apache/james/rrt/lib/MappingsImpl.java b/server/data/data-library/src/main/java/org/apache/james/rrt/lib/MappingsImpl.java
index 6c042ff..c633307 100644
--- a/server/data/data-library/src/main/java/org/apache/james/rrt/lib/MappingsImpl.java
+++ b/server/data/data-library/src/main/java/org/apache/james/rrt/lib/MappingsImpl.java
@@ -22,6 +22,7 @@ package org.apache.james.rrt.lib;
 
 import java.io.Serializable;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.Iterator;
@@ -86,7 +87,11 @@ public class MappingsImpl implements Mappings, Serializable {
     public static MappingsImpl fromRawString(String raw) {
         return fromCollection(mappingToCollection(raw));
     }
-    
+
+    public static MappingsImpl fromMappings(Mapping... mappings) {
+        return fromMappings(Arrays.stream(mappings));
+    }
+
     private static ArrayList<String> mappingToCollection(String rawMapping) {
         ArrayList<String> map = new ArrayList<>();
         StringTokenizer tokenizer = new StringTokenizer(rawMapping, RecipientRewriteTableUtil.getSeparator(rawMapping));

http://git-wip-us.apache.org/repos/asf/james-project/blob/dc59e58b/server/protocols/webadmin/webadmin-data/pom.xml
----------------------------------------------------------------------
diff --git a/server/protocols/webadmin/webadmin-data/pom.xml b/server/protocols/webadmin/webadmin-data/pom.xml
index f4b1d20..c007063 100644
--- a/server/protocols/webadmin/webadmin-data/pom.xml
+++ b/server/protocols/webadmin/webadmin-data/pom.xml
@@ -19,7 +19,6 @@
 -->
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <modelVersion>4.0.0</modelVersion>
-
     <parent>
         <groupId>org.apache.james</groupId>
         <artifactId>james-server</artifactId>
@@ -119,6 +118,11 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-params</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
             <groupId>org.junit.platform</groupId>
             <artifactId>junit-platform-launcher</artifactId>
             <scope>test</scope>

http://git-wip-us.apache.org/repos/asf/james-project/blob/dc59e58b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/DomainMappingsRoutes.java
----------------------------------------------------------------------
diff --git a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/DomainMappingsRoutes.java b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/DomainMappingsRoutes.java
new file mode 100644
index 0000000..2502fa7
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/DomainMappingsRoutes.java
@@ -0,0 +1,206 @@
+/****************************************************************
+ * 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 org.apache.james.webadmin.routes.DomainMappingsRoutes.DOMAIN_MAPPINGS;
+import static spark.Spark.halt;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+
+import javax.inject.Inject;
+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.Domain;
+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.MappingSource;
+import org.apache.james.rrt.lib.Mappings;
+import org.apache.james.webadmin.Routes;
+import org.apache.james.webadmin.utils.ErrorResponder;
+import org.apache.james.webadmin.utils.JsonTransformer;
+import org.eclipse.jetty.http.HttpStatus;
+
+import com.github.fge.lambdas.consumers.ThrowingBiConsumer;
+import com.github.steveash.guavate.Guavate;
+
+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 = "Domain Mappings")
+@Path(DOMAIN_MAPPINGS)
+@Produces("application/json")
+public class DomainMappingsRoutes implements Routes {
+    static final String DOMAIN_MAPPINGS = "/domainMappings";
+    private static final String FROM_DOMAIN = "fromDomain";
+    private static final String SPECIFIC_MAPPING_PATH = SEPARATOR + "/{" + FROM_DOMAIN + "}";
+    private static final String SPECIFIC_MAPPING = DOMAIN_MAPPINGS + SEPARATOR + ":" + FROM_DOMAIN;
+
+    private final RecipientRewriteTable recipientRewriteTable;
+    private final JsonTransformer jsonTransformer;
+
+    @Inject
+    public DomainMappingsRoutes(final RecipientRewriteTable recipientRewriteTable, final JsonTransformer jsonTransformer) {
+        this.recipientRewriteTable = recipientRewriteTable;
+        this.jsonTransformer = jsonTransformer;
+    }
+
+    @Override
+    public void define(final Service service) {
+        service.get(DOMAIN_MAPPINGS, this::getAllMappings, jsonTransformer);
+        service.get(SPECIFIC_MAPPING, this::getMapping, jsonTransformer);
+        service.put(SPECIFIC_MAPPING, this::addDomainMapping);
+        service.delete(SPECIFIC_MAPPING, this::removeDomainMapping);
+    }
+
+    @PUT
+    @Path(SPECIFIC_MAPPING_PATH)
+    @ApiOperation(value = "Creating domain mapping between source and destination domains.")
+    @ApiImplicitParams({
+            @ApiImplicitParam(required = true, dataType = "string", name = FROM_DOMAIN, paramType = "path")
+    })
+    @ApiResponses(value = {
+            @ApiResponse(code = HttpStatus.NO_CONTENT_204, message = "Ok"),
+            @ApiResponse(code = HttpStatus.BAD_REQUEST_400, message = "Domain name is invalid"),
+            @ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500,
+                    message = "Internal server error - Something went bad on the server side.")
+    })
+    public HaltException addDomainMapping(Request request, Response response) {
+        doMapping(request, recipientRewriteTable::addAliasDomainMapping);
+        return halt(HttpStatus.NO_CONTENT_204);
+    }
+
+    @DELETE
+    @Path(SPECIFIC_MAPPING_PATH)
+    @ApiOperation(value = "Removes domain mapping between source and destination domains.")
+    @ApiImplicitParams({
+            @ApiImplicitParam(required = true, dataType = "string", name = FROM_DOMAIN, paramType = "path")
+    })
+    @ApiResponses(value = {
+            @ApiResponse(code = HttpStatus.NO_CONTENT_204, message = "Ok"),
+            @ApiResponse(code = HttpStatus.BAD_REQUEST_400, message = "Domain name is invalid"),
+            @ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500,
+                    message = "Internal server error - Something went bad on the server side.")
+    })
+    public HaltException removeDomainMapping(Request request, Response response) {
+        doMapping(request, recipientRewriteTable::removeAliasDomainMapping);
+        return halt(HttpStatus.NO_CONTENT_204);
+    }
+
+    @GET
+    @Path(DOMAIN_MAPPINGS)
+    @ApiOperation(value = "Lists all domain mappings.")
+    @ApiResponses(value = {
+            @ApiResponse(code = HttpStatus.OK_200, message = "Domain mappings.", responseContainer = "Map"),
+            @ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500,
+                    message = "Internal server error - Something went bad on the server side.")
+    })
+    public Map<String, List<String>> getAllMappings(Request request, Response response) throws RecipientRewriteTableException {
+        return recipientRewriteTable.getAllMappings()
+                .entrySet()
+                .stream()
+                .filter(mappingsEntry -> !mappingsEntry.getValue().isEmpty())
+                .filter(mappingsEntry -> mappingsEntry.getValue().contains(Mapping.Type.Domain))
+                .collect(Guavate.toImmutableMap(
+                        mappingsEntry -> mappingsEntry.getKey().getFixedDomain(),
+                        mappingsEntry -> toDomainList(mappingsEntry.getValue())
+                ));
+    }
+
+    @GET
+    @Path(SPECIFIC_MAPPING_PATH)
+    @ApiOperation(value = "Lists mappings for specific domain.")
+    @ApiImplicitParams({
+            @ApiImplicitParam(required = true, dataType = "string", name = FROM_DOMAIN, paramType = "path")
+    })
+    @ApiResponses(value = {
+            @ApiResponse(code = HttpStatus.OK_200, message = "Domain mappings.", responseContainer = "List"),
+            @ApiResponse(code = HttpStatus.NOT_FOUND_404, message = "Not existing mappings."),
+            @ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500,
+                    message = "Internal server error - Something went bad on the server side.")
+    })
+    public List<String> getMapping(Request request, Response response) throws RecipientRewriteTableException {
+        MappingSource mappingSource = mappingSourceFrom(request);
+
+        return Optional.ofNullable(recipientRewriteTable.getUserDomainMappings(mappingSource).select(Mapping.Type.Domain))
+                .filter(mappings -> !mappings.isEmpty())
+                .filter(mappings -> mappings.contains(Mapping.Type.Domain))
+                .map(this::toDomainList)
+                .orElseThrow(() -> ErrorResponder.builder()
+                        .statusCode(HttpStatus.NOT_FOUND_404)
+                        .type(ErrorResponder.ErrorType.NOT_FOUND)
+                        .message(String.format("Cannot find mappings for %s", mappingSource.getFixedDomain()))
+                        .haltError());
+    }
+
+    private MappingSource mappingSourceFrom(final Request request) {
+        return createDomainOrThrow()
+                .andThen(MappingSource::fromDomain)
+                .apply(request.params(FROM_DOMAIN));
+    }
+
+    private void doMapping(Request request, ThrowingBiConsumer<MappingSource, Domain> mappingOperation) {
+        MappingSource fromDomain = mappingSourceFrom(request);
+
+        Domain toDomain = createDomainOrThrow().apply(request.body());
+
+        mappingOperation.accept(fromDomain, toDomain);
+    }
+
+    private Function<String, Domain> createDomainOrThrow() {
+        return candidate -> {
+            try {
+                return Domain.of(candidate.trim());
+            } catch (IllegalArgumentException e) {
+                throw ErrorResponder.builder()
+                        .statusCode(HttpStatus.BAD_REQUEST_400)
+                        .type(ErrorResponder.ErrorType.INVALID_ARGUMENT)
+                        .message(String.format("The domain %s is invalid.", candidate))
+                        .cause(e)
+                        .haltError();
+            }
+        };
+    }
+
+    private List<String> toDomainList(Mappings mappings) {
+        return mappings
+                .select(Mapping.Type.Domain)
+                .asStream()
+                .map(Mapping::asString)
+                .map(Mapping.Type.Domain::withoutPrefix)
+                .collect(Guavate.toImmutableList());
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/dc59e58b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/DomainMappingsRoutesTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/DomainMappingsRoutesTest.java b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/DomainMappingsRoutesTest.java
new file mode 100644
index 0000000..b41c1ba
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/DomainMappingsRoutesTest.java
@@ -0,0 +1,386 @@
+/****************************************************************
+ * 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.delete;
+import static com.jayway.restassured.RestAssured.given;
+import static com.jayway.restassured.RestAssured.put;
+import static com.jayway.restassured.RestAssured.when;
+import static org.apache.james.webadmin.WebAdminServer.NO_CONFIGURATION;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.Matchers.isEmptyString;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+import org.apache.james.core.Domain;
+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.lib.Mapping;
+import org.apache.james.rrt.lib.MappingSource;
+import org.apache.james.rrt.lib.Mappings;
+import org.apache.james.rrt.lib.MappingsImpl;
+import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
+import org.apache.james.webadmin.WebAdminServer;
+import org.apache.james.webadmin.WebAdminUtils;
+import org.apache.james.webadmin.utils.ErrorResponder;
+import org.apache.james.webadmin.utils.JsonTransformer;
+import org.eclipse.jetty.http.HttpStatus;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assumptions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.jayway.restassured.RestAssured;
+import com.jayway.restassured.filter.log.LogDetail;
+import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.response.Response;
+import com.jayway.restassured.specification.RequestSpecification;
+
+class DomainMappingsRoutesTest {
+    private RecipientRewriteTable recipientRewriteTable;
+    private WebAdminServer webAdminServer;
+
+    private void createServer(DomainMappingsRoutes domainMappingsRoutes) throws Exception {
+        webAdminServer = WebAdminUtils.createWebAdminServer(new DefaultMetricFactory(), domainMappingsRoutes);
+        webAdminServer.configure(NO_CONFIGURATION);
+        webAdminServer.await();
+
+        RestAssured.requestSpecification = WebAdminUtils.buildRequestSpecification(webAdminServer)
+                .setBasePath("/domainMappings")
+                .log(LogDetail.METHOD)
+                .build();
+    }
+
+    @BeforeEach
+    void setUp() throws Exception {
+        recipientRewriteTable = spy(new MemoryRecipientRewriteTable());
+
+        createServer(new DomainMappingsRoutes(recipientRewriteTable, new JsonTransformer()));
+    }
+
+    @AfterEach
+    void tearDown() {
+        webAdminServer.destroy();
+    }
+
+    @Nested
+    class NormalBehaviour {
+
+        @Test
+        void addDomainMappingShouldRespondWithNoContent() {
+            given()
+                .body("to.com")
+            .when()
+                .put("from.com")
+            .then()
+                .statusCode(HttpStatus.NO_CONTENT_204)
+                .body(isEmptyString());
+        }
+
+        @Test
+        void getDomainMappingsShouldReturnAllDomainMappings() throws RecipientRewriteTableException {
+            String alias1 = "to_1.com";
+            String alias2 = "to_2.com";
+            String alias3 = "to_3.com";
+
+            Domain expectedDomain = Domain.of("abc.com");
+            MappingSource mappingSource = MappingSource.fromDomain(expectedDomain);
+
+            recipientRewriteTable.addAliasDomainMapping(mappingSource, Domain.of(alias1));
+            recipientRewriteTable.addAliasDomainMapping(mappingSource, Domain.of(alias2));
+            recipientRewriteTable.addAliasDomainMapping(mappingSource, Domain.of(alias3));
+
+            Map<String, List<String>> map =
+                    when()
+                        .get()
+                    .then()
+                        .contentType(ContentType.JSON)
+                        .statusCode(HttpStatus.OK_200)
+                    .extract()
+                        .body()
+                        .jsonPath()
+                        .getMap(".");
+
+            assertThat(map)
+                    .containsOnly(entry(expectedDomain.name(), ImmutableList.of(alias1, alias2, alias3)));
+        }
+
+        @Test
+        void getDomainMappingsEmptyMappingsAreFilteredOut() throws RecipientRewriteTableException {
+            MappingSource nonEmptyMapping = MappingSource.fromDomain(Domain.of("abc.com"));
+            MappingSource emptyMapping = MappingSource.fromDomain(Domain.of("def.com"));
+
+            Map<MappingSource, Mappings> mappings = ImmutableMap.of(
+                    nonEmptyMapping, MappingsImpl.fromRawString("domain:a.com"),
+                    emptyMapping, MappingsImpl.empty()
+            );
+
+            when(recipientRewriteTable.getAllMappings()).thenReturn(mappings);
+
+            Map<String, List<String>> map =
+                    when()
+                        .get()
+                    .then()
+                        .contentType(ContentType.JSON)
+                        .statusCode(HttpStatus.OK_200)
+                    .extract()
+                        .body()
+                        .jsonPath()
+                        .getMap(".");
+
+            assertThat(map)
+                    .containsKey(nonEmptyMapping.asString())
+                    .doesNotContainKey(emptyMapping.asString());
+        }
+
+        @Test
+        void getDomainMappingsShouldFilterNonDomainMappings() throws RecipientRewriteTableException {
+            MappingSource mappingSource = MappingSource.fromDomain(Domain.of("abc.com"));
+            String address = "addr@domain.com";
+
+            recipientRewriteTable.addAddressMapping(mappingSource, address);
+            recipientRewriteTable.addForwardMapping(mappingSource, address);
+            recipientRewriteTable.addErrorMapping(mappingSource, address);
+            recipientRewriteTable.addGroupMapping(mappingSource, address);
+            recipientRewriteTable.addRegexMapping(mappingSource, address);
+
+            when()
+                .get()
+            .then()
+                .contentType(ContentType.JSON)
+                .statusCode(HttpStatus.OK_200)
+                .body(is("{}"));
+        }
+
+        @Test
+        void getDomainMappingsShouldBeEmptyByDefault() {
+            when()
+                .get()
+            .then()
+                .contentType(ContentType.JSON)
+                .statusCode(HttpStatus.OK_200)
+                .body(is("{}"));
+        }
+
+        @Test
+        void deleteDomainMappingShouldRespondWithNoContent() {
+            given()
+                .body("to.com")
+            .when()
+                .delete("from.com")
+            .then()
+                .statusCode(HttpStatus.NO_CONTENT_204)
+                .body(isEmptyString());
+        }
+
+        @Test
+        void deleteDomainMappingShouldRemoveMapping() throws RecipientRewriteTableException {
+            MappingSource mappingSource = MappingSource.fromDomain(Domain.of("from.com"));
+            String alias = "to.com";
+
+            recipientRewriteTable.addAliasDomainMapping(mappingSource, Domain.of(alias));
+
+            Assumptions.assumeTrue(recipientRewriteTable.getUserDomainMappings(mappingSource) != null);
+
+            given()
+                .body("to.com")
+            .when()
+                .delete("from.com")
+            .then()
+                .body(isEmptyString());
+
+            assertThat(recipientRewriteTable.getAllMappings()).isEmpty();
+        }
+
+        @Test
+        void getSpecificDomainMappingShouldRespondWithNotFoundWhenHasNoAliases() throws RecipientRewriteTableException {
+            String domain = "from.com";
+
+            when(recipientRewriteTable.getUserDomainMappings(any())).thenReturn(MappingsImpl.empty());
+
+            when()
+                .get(domain)
+            .then()
+                .contentType(ContentType.JSON)
+                .statusCode(HttpStatus.NOT_FOUND_404)
+                .body("type", is(ErrorResponder.ErrorType.NOT_FOUND.getType()))
+                .body("statusCode", is(HttpStatus.NOT_FOUND_404))
+                .body("message", is("Cannot find mappings for " + domain));
+        }
+
+        @Test
+        void getSpecificDomainMappingShouldRespondWithNotFoundWhenHasEmptyAliases() throws RecipientRewriteTableException {
+            String domain = "from.com";
+
+            when(recipientRewriteTable.getUserDomainMappings(any())).thenReturn(MappingsImpl.empty());
+
+            when()
+                .get(domain)
+            .then()
+                .contentType(ContentType.JSON)
+                .statusCode(HttpStatus.NOT_FOUND_404)
+                .body("type", is(ErrorResponder.ErrorType.NOT_FOUND.getType()))
+                .body("statusCode", is(HttpStatus.NOT_FOUND_404))
+                .body("message", is("Cannot find mappings for " + domain));
+        }
+
+        @Test
+        void getSpecificDomainMappingShouldFilterOutNonDomainMappings() throws RecipientRewriteTableException {
+            String domain = "from.com";
+            String aliasDomain = "to.com";
+            final MappingSource mappingSource = MappingSource.fromDomain(Domain.of(domain));
+
+            recipientRewriteTable.addRegexMapping(mappingSource, "(.*)@localhost");
+            recipientRewriteTable.addGroupMapping(mappingSource, "user@domain.com");
+            recipientRewriteTable.addForwardMapping(mappingSource, "user@domain.com");
+            recipientRewriteTable.addErrorMapping(mappingSource, "disabled");
+            recipientRewriteTable.addAliasDomainMapping(mappingSource, Domain.of(aliasDomain));
+
+            List<String> body =
+                    when()
+                        .get(domain)
+                    .then()
+                        .contentType(ContentType.JSON)
+                        .statusCode(HttpStatus.OK_200)
+                    .extract()
+                        .jsonPath()
+                        .getList(".");
+
+            assertThat(body).containsOnly(aliasDomain);
+        }
+
+        @Test
+        void getSpecificDomainMappingShouldReturnDomainMappings() throws RecipientRewriteTableException {
+            String domain = "abc.com";
+            String aliasDomain = "a.com";
+            Mappings mappings = MappingsImpl.fromMappings(Mapping.domain(Domain.of(aliasDomain)));
+
+            when(recipientRewriteTable.getUserDomainMappings(any())).thenReturn(mappings);
+
+            List<String> body =
+            when()
+                .get(domain)
+            .then()
+                .contentType(ContentType.JSON)
+                .statusCode(HttpStatus.OK_200)
+                .extract()
+                .jsonPath()
+                .getList(".");
+
+            assertThat(body).contains(aliasDomain);
+        }
+    }
+
+    @Nested
+    class IllegalInputs {
+        @Test
+        void addDomainMappingShouldRespondWithNotFound() {
+            when()
+                .put("")
+            .then()
+                .statusCode(HttpStatus.NOT_FOUND_404);
+        }
+
+        @Test
+        void deleteDomainMappingShouldRespondWithNotFound() {
+            when()
+                .delete("")
+            .then()
+                .statusCode(HttpStatus.NOT_FOUND_404);
+        }
+
+        @Test
+        void addDomainMappingWithInvalidDomainInBody() {
+            assertBadRequest("abc@domain.com", spec -> put("domain.com"));
+        }
+
+        @Test
+        void deleteDomainMappingWithInvalidDomainInBody() {
+            assertBadRequest("abc@domain.com", spec -> put("domain.com"));
+        }
+
+        @Test
+        void addDomainMappingWithInvalidDomainInPath() {
+            assertBadRequest("domain.com", spec -> put("abc@domain.com"));
+        }
+
+        @Test
+        void deleteDomainMappingWithInvalidDomainInPath() {
+            assertBadRequest("domain.com", spec -> put("abc@domain.com"));
+        }
+
+        @Test
+        void addDomainMappingWithEmptyAliasDomain() {
+            assertBadRequest("", spec -> put("domain.com"));
+        }
+
+        @Test
+        void deleteDomainMappingWithEmptyAliasDomain() {
+            assertBadRequest("", spec -> delete("domain.com"));
+        }
+
+
+        @Test
+        void addSpecificDomainMappingWithInvalidDomainInPath() {
+            Map<String, Object> errors =
+            when()
+                .get("abc@domain.com")
+            .then()
+                .statusCode(HttpStatus.BAD_REQUEST_400)
+                .contentType(ContentType.JSON)
+            .extract()
+                .body()
+                .jsonPath()
+                .getMap(".");
+
+            assertThat(errors)
+                    .containsEntry("statusCode", HttpStatus.BAD_REQUEST_400)
+                    .containsEntry("type", "InvalidArgument")
+                    .hasEntrySatisfying("message", o -> assertThat((String) o).matches("^The domain .* is invalid\\.$"));
+        }
+
+        private void assertBadRequest(String toDomain, Function<RequestSpecification, Response> op) {
+            Map<String, Object> errors = op.apply(given().body(toDomain).when())
+                    .then()
+                        .statusCode(HttpStatus.BAD_REQUEST_400)
+                        .contentType(ContentType.JSON)
+                    .extract()
+                        .body()
+                        .jsonPath()
+                        .getMap(".");
+
+            assertThat(errors)
+                    .containsEntry("statusCode", HttpStatus.BAD_REQUEST_400)
+                    .containsEntry("type", "InvalidArgument")
+                    .hasEntrySatisfying("message", o -> assertThat((String) o).matches("^The domain .* is invalid\\.$"));
+        }
+    }
+}
\ No newline at end of file


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org