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 bt...@apache.org on 2019/05/16 08:48:17 UTC

[james-project] 02/23: JAMES-2149 Domain routes should handle aliases

This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit abaa0021c6c9f2d17d84f84052b77f0959c984d0
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Mon May 13 17:50:40 2019 +0700

    JAMES-2149 Domain routes should handle aliases
    
    Add the following CRUD routes and their unit tests:
    
    GET /domains/domain.tld/aliases
    PUT /domains/domain.tld/aliases/alias.domain.tld
    DELETE /domains/domain.tld/aliases/alias.domain.tld
---
 .../james/webadmin/dto/DomainAliasResponse.java    |  51 ++++
 .../james/webadmin/routes/DomainsRoutes.java       | 162 +++++++++++--
 .../webadmin/dto/DomainAliasResponseTest.java      |  31 +++
 .../james/webadmin/routes/DomainsRoutesTest.java   | 257 ++++++++++++++++++++-
 4 files changed, 479 insertions(+), 22 deletions(-)

diff --git a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/dto/DomainAliasResponse.java b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/dto/DomainAliasResponse.java
new file mode 100644
index 0000000..141faf9
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/dto/DomainAliasResponse.java
@@ -0,0 +1,51 @@
+/****************************************************************
+ * 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.dto;
+
+import java.util.Objects;
+
+import org.apache.james.rrt.lib.MappingSource;
+
+public class DomainAliasResponse {
+    private final MappingSource source;
+
+    public DomainAliasResponse(MappingSource source) {
+        this.source = source;
+    }
+
+    public String getSource() {
+        return source.getFixedDomain();
+    }
+
+    @Override
+    public final boolean equals(Object o) {
+        if (o instanceof DomainAliasResponse) {
+            DomainAliasResponse that = (DomainAliasResponse) o;
+
+            return Objects.equals(this.source, that.source);
+        }
+        return false;
+    }
+
+    @Override
+    public final int hashCode() {
+        return Objects.hash(source);
+    }
+}
diff --git a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/DomainsRoutes.java b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/DomainsRoutes.java
index 321b2c7..8625498 100644
--- a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/DomainsRoutes.java
+++ b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/DomainsRoutes.java
@@ -34,8 +34,13 @@ import javax.ws.rs.Produces;
 import org.apache.james.core.Domain;
 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.MappingSource;
 import org.apache.james.webadmin.Constants;
 import org.apache.james.webadmin.Routes;
+import org.apache.james.webadmin.dto.DomainAliasResponse;
 import org.apache.james.webadmin.utils.ErrorResponder;
 import org.apache.james.webadmin.utils.ErrorResponder.ErrorType;
 import org.apache.james.webadmin.utils.JsonTransformer;
@@ -43,13 +48,18 @@ import org.eclipse.jetty.http.HttpStatus;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.github.steveash.guavate.Guavate;
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
 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;
@@ -58,22 +68,31 @@ import spark.Service;
 @Path(DomainsRoutes.DOMAINS)
 @Produces("application/json")
 public class DomainsRoutes implements Routes {
-
-    private static final String DOMAIN_NAME = ":domainName";
-    private static final Logger LOGGER = LoggerFactory.getLogger(DomainsRoutes.class);
+    @FunctionalInterface
+    interface MappingOperation {
+        void perform(MappingSource mappingSource, Mapping mapping) throws RecipientRewriteTableException;
+    }
 
     public static final String DOMAINS = "/domains";
-    public static final String SPECIFIC_DOMAIN = DOMAINS + SEPARATOR + DOMAIN_NAME;
-    public static final int MAXIMUM_DOMAIN_SIZE = 256;
-
+    private static final Logger LOGGER = LoggerFactory.getLogger(DomainsRoutes.class);
+    private static final String DOMAIN_NAME = ":domainName";
+    private static final String SOURCE_DOMAIN = ":sourceDomain";
+    private static final String DESTINATION_DOMAIN = ":destinationDomain";
+    private static final String SPECIFIC_DOMAIN = DOMAINS + SEPARATOR + DOMAIN_NAME;
+    private static final String ALIASES = "aliases";
+    private static final String DOMAIN_ALIASES = SPECIFIC_DOMAIN + SEPARATOR + ALIASES;
+    private static final String SPECIFIC_ALIAS = DOMAINS + SEPARATOR + DESTINATION_DOMAIN + SEPARATOR + ALIASES + SEPARATOR + SOURCE_DOMAIN;
+    private static final int MAXIMUM_DOMAIN_SIZE = 256;
 
     private final DomainList domainList;
+    private final RecipientRewriteTable recipientRewriteTable;
     private final JsonTransformer jsonTransformer;
     private Service service;
 
     @Inject
-    public DomainsRoutes(DomainList domainList, JsonTransformer jsonTransformer) {
+    public DomainsRoutes(DomainList domainList, RecipientRewriteTable recipientRewriteTable, JsonTransformer jsonTransformer) {
         this.domainList = domainList;
+        this.recipientRewriteTable = recipientRewriteTable;
         this.jsonTransformer = jsonTransformer;
     }
 
@@ -86,13 +105,16 @@ public class DomainsRoutes implements Routes {
     public void define(Service service) {
         this.service = service;
 
+        // Domain endpoints
         defineGetDomains();
-
         defineDomainExists();
-
         defineAddDomain();
-
         defineDeleteDomain();
+
+        // Domain aliases endpoints
+        defineListAliases(service);
+        defineAddAlias(service);
+        defineRemoveAlias(service);
     }
 
     @DELETE
@@ -156,9 +178,59 @@ public class DomainsRoutes implements Routes {
             jsonTransformer);
     }
 
+    @GET
+    @Path("/{domainName}/aliases")
+    @ApiOperation(value = "Getting all aliases for a domain")
+    @ApiImplicitParams({
+        @ApiImplicitParam(required = true, dataType = "string", name = "domainName", paramType = "path")
+    })
+    @ApiResponses(value = {
+        @ApiResponse(code = HttpStatus.OK_200, message = "OK", response = List.class),
+        @ApiResponse(code = HttpStatus.NOT_FOUND_404, message = "The domain does not exist."),
+        @ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500,
+            message = "Internal server error - Something went bad on the server side.")
+    })
+    public void defineListAliases(Service service) {
+        service.get(DOMAIN_ALIASES, this::listDomainAliases, jsonTransformer);
+    }
+
+    @DELETE
+    @Path("/{destinationDomain}/aliases/{sourceDomain}")
+    @ApiOperation(value = "Remove an alias for a specific domain")
+    @ApiImplicitParams({
+        @ApiImplicitParam(required = true, dataType = "string", name = "sourceDomain", paramType = "path"),
+        @ApiImplicitParam(required = true, dataType = "string", name = "destinationDomain", paramType = "path")
+    })
+    @ApiResponses(value = {
+        @ApiResponse(code = HttpStatus.NO_CONTENT_204, message = "OK", response = List.class),
+        @ApiResponse(code = HttpStatus.NOT_FOUND_404, message = "The domain does not exist."),
+        @ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500,
+            message = "Internal server error - Something went bad on the server side.")
+    })
+    public void defineRemoveAlias(Service service) {
+        service.delete(SPECIFIC_ALIAS, this::removeDomainAlias, jsonTransformer);
+    }
+
+    @PUT
+    @Path("/{destinationDomain}/aliases/{sourceDomain}")
+    @ApiOperation(value = "Add an alias for a specific domain")
+    @ApiImplicitParams({
+        @ApiImplicitParam(required = true, dataType = "string", name = "sourceDomain", paramType = "path"),
+        @ApiImplicitParam(required = true, dataType = "string", name = "destinationDomain", paramType = "path")
+    })
+    @ApiResponses(value = {
+        @ApiResponse(code = HttpStatus.NO_CONTENT_204, message = "OK", response = List.class),
+        @ApiResponse(code = HttpStatus.NOT_FOUND_404, message = "The domain does not exist."),
+        @ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500,
+            message = "Internal server error - Something went bad on the server side.")
+    })
+    public void defineAddAlias(Service service) {
+        service.put(SPECIFIC_ALIAS, this::addDomainAlias, jsonTransformer);
+    }
+
     private String removeDomain(Request request, Response response) {
         try {
-            Domain domain = checkValidDomain(request);
+            Domain domain = checkValidDomain(request.params(DOMAIN_NAME));
             domainList.removeDomain(domain);
         } catch (DomainListException e) {
             LOGGER.info("{} did not exists", request.params(DOMAIN_NAME));
@@ -168,7 +240,7 @@ public class DomainsRoutes implements Routes {
     }
 
     private String addDomain(Request request, Response response) {
-        Domain domain = checkValidDomain(request);
+        Domain domain = checkValidDomain(request.params(DOMAIN_NAME));
         try {
             addDomain(domain);
             response.status(204);
@@ -192,8 +264,7 @@ public class DomainsRoutes implements Routes {
         return Constants.EMPTY_BODY;
     }
 
-    private Domain checkValidDomain(Request request) {
-        String domainName = request.params(DOMAIN_NAME);
+    private Domain checkValidDomain(String domainName) {
         try {
             return Domain.of(domainName);
         } catch (IllegalArgumentException e) {
@@ -212,16 +283,67 @@ public class DomainsRoutes implements Routes {
     }
 
     private Response exists(Request request, Response response) throws DomainListException {
-        Domain domain = checkValidDomain(request);
+        Domain domain = checkValidDomain(request.params(DOMAIN_NAME));
+
         if (!domainList.containsDomain(domain)) {
-            throw ErrorResponder.builder()
-                .statusCode(HttpStatus.NOT_FOUND_404)
-                .type(ErrorType.INVALID_ARGUMENT)
-                .message("The domain list does not contain: " + domain.name())
-                .haltError();
+            throw domainNotFound(domain);
         } else {
             response.status(HttpStatus.NO_CONTENT_204);
             return response;
         }
     }
+
+    private ImmutableSet<DomainAliasResponse> listDomainAliases(Request request, Response response) throws DomainListException, RecipientRewriteTableException {
+        Domain domain = checkValidDomain(request.params(DOMAIN_NAME));
+
+        if (!hasAliases(domain)) {
+            throw domainHasNoAliases(domain);
+        } else {
+            return recipientRewriteTable.listSources(Mapping.domain(domain))
+                .map(DomainAliasResponse::new)
+                .collect(Guavate.toImmutableSet());
+        }
+    }
+
+    private String addDomainAlias(Request request, Response response) throws DomainListException, RecipientRewriteTableException {
+        return performOperationOnAlias(request, response, recipientRewriteTable::addMapping);
+    }
+
+    private String removeDomainAlias(Request request, Response response) throws DomainListException, RecipientRewriteTableException {
+        return performOperationOnAlias(request, response, recipientRewriteTable::removeMapping);
+    }
+
+    private String performOperationOnAlias(Request request, Response response, MappingOperation operation) throws DomainListException, RecipientRewriteTableException {
+        Domain sourceDomain = checkValidDomain(request.params(SOURCE_DOMAIN));
+        Domain destinationDomain = checkValidDomain(request.params(DESTINATION_DOMAIN));
+
+        if (!domainList.containsDomain(sourceDomain)) {
+            throw domainNotFound(sourceDomain);
+        }
+
+        operation.perform(MappingSource.fromDomain(sourceDomain), Mapping.domain(destinationDomain));
+        response.status(HttpStatus.NO_CONTENT_204);
+        return Constants.EMPTY_BODY;
+    }
+
+    private boolean hasAliases(Domain domain) throws DomainListException, RecipientRewriteTableException {
+        return domainList.containsDomain(domain)
+            || recipientRewriteTable.listSources(Mapping.domain(domain)).findFirst().isPresent();
+    }
+
+    private HaltException domainNotFound(Domain domain) {
+        return ErrorResponder.builder()
+            .statusCode(HttpStatus.NOT_FOUND_404)
+            .type(ErrorType.INVALID_ARGUMENT)
+            .message("The domain list does not contain: " + domain.name())
+            .haltError();
+    }
+
+    private HaltException domainHasNoAliases(Domain domain) {
+        return ErrorResponder.builder()
+            .statusCode(HttpStatus.NOT_FOUND_404)
+            .type(ErrorType.INVALID_ARGUMENT)
+            .message("The following domain is not in the domain list and has no registered local aliases: " + domain.name())
+            .haltError();
+    }
 }
diff --git a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/dto/DomainAliasResponseTest.java b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/dto/DomainAliasResponseTest.java
new file mode 100644
index 0000000..f8fb107
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/dto/DomainAliasResponseTest.java
@@ -0,0 +1,31 @@
+/****************************************************************
+ * 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.dto;
+
+import org.junit.jupiter.api.Test;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+class DomainAliasResponseTest {
+    @Test
+    void shouldMatchBeanContract() {
+        EqualsVerifier.forClass(DomainAliasResponse.class).verify();
+    }
+}
\ No newline at end of file
diff --git a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/DomainsRoutesTest.java b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/DomainsRoutesTest.java
index 82b7dde..d5f28bd 100644
--- a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/DomainsRoutesTest.java
+++ b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/DomainsRoutesTest.java
@@ -25,6 +25,9 @@ import static io.restassured.RestAssured.with;
 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.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.hasSize;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
@@ -40,6 +43,7 @@ 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.memory.MemoryRecipientRewriteTable;
 import org.apache.james.webadmin.WebAdminServer;
 import org.apache.james.webadmin.WebAdminUtils;
 import org.apache.james.webadmin.utils.JsonTransformer;
@@ -54,14 +58,17 @@ import io.restassured.http.ContentType;
 
 
 public class DomainsRoutesTest {
-    public static final String DOMAIN = "domain";
+    private static final String DOMAIN = "domain";
+    private static final String ALIAS_DOMAIN = "alias.domain";
+    private static final String ALIAS_DOMAIN_2 = "alias.domain.bis";
+    public static final String EXTERNAL_DOMAIN = "external.domain.tld";
 
     private WebAdminServer webAdminServer;
 
     private void createServer(DomainList domainList) throws Exception {
         webAdminServer = WebAdminUtils.createWebAdminServer(
             new DefaultMetricFactory(),
-            new DomainsRoutes(domainList, new JsonTransformer()));
+            new DomainsRoutes(domainList, new MemoryRecipientRewriteTable(), new JsonTransformer()));
         webAdminServer.configure(NO_CONFIGURATION);
         webAdminServer.await();
 
@@ -255,6 +262,252 @@ public class DomainsRoutesTest {
                 .statusCode(HttpStatus.NOT_FOUND_404);
         }
 
+        @Nested
+        class DomainAlias{
+            @Test
+            void getAliasesShouldReturnNotFoundWhenDomainDoesNotExist() {
+                when()
+                    .get(DOMAIN + "/aliases")
+                .then()
+                    .contentType(ContentType.JSON)
+                    .statusCode(HttpStatus.NOT_FOUND_404)
+                    .body("statusCode", is(HttpStatus.NOT_FOUND_404))
+                    .body("type", is("InvalidArgument"))
+                    .body("message", is("The following domain is not in the domain list and has no registered local aliases: domain"));
+            }
+
+            @Test
+            void getAliasesShouldReturnEmptyWhenNone() {
+                with().put(DOMAIN);
+
+                when()
+                    .get(DOMAIN + "/aliases")
+                .then()
+                    .contentType(ContentType.JSON)
+                    .statusCode(HttpStatus.OK_200)
+                    .body(".", hasSize(0));
+            }
+
+            @Test
+            void getAliasesShouldReturnCreatedAliases() {
+                with().put(DOMAIN);
+                with().put(ALIAS_DOMAIN);
+                with().put(ALIAS_DOMAIN_2);
+
+                with().put(DOMAIN + "/aliases/" + ALIAS_DOMAIN);
+                with().put(DOMAIN + "/aliases/" + ALIAS_DOMAIN_2);
+
+                when()
+                    .get(DOMAIN + "/aliases")
+                .then()
+                    .contentType(ContentType.JSON)
+                    .statusCode(HttpStatus.OK_200)
+                    .body("source", containsInAnyOrder(ALIAS_DOMAIN, ALIAS_DOMAIN_2));
+            }
+
+            @Test
+            void putShouldBeIdempotent() {
+                with().put(DOMAIN);
+                with().put(ALIAS_DOMAIN);
+
+                with().put(DOMAIN + "/aliases/" + ALIAS_DOMAIN);
+                with().put(DOMAIN + "/aliases/" + ALIAS_DOMAIN);
+
+                when()
+                    .get(DOMAIN + "/aliases")
+                .then()
+                    .contentType(ContentType.JSON)
+                    .statusCode(HttpStatus.OK_200)
+                    .body("source", containsInAnyOrder(ALIAS_DOMAIN));
+            }
+
+            @Test
+            void deleteShouldNotFailOnNonExistingEvents() {
+                with().put(DOMAIN);
+                with().put(ALIAS_DOMAIN);
+
+                with().delete(DOMAIN + "/aliases/" + ALIAS_DOMAIN);
+
+                when()
+                    .get(DOMAIN + "/aliases")
+                .then()
+                    .contentType(ContentType.JSON)
+                    .statusCode(HttpStatus.OK_200)
+                    .body("", hasSize(0));
+            }
+
+            @Test
+            void putShouldLowercaseDomain() {
+                with().put(DOMAIN);
+                with().put(ALIAS_DOMAIN);
+
+                with().put(DOMAIN + "/aliases/" + "Alias.Domain");
+
+                when()
+                    .get(DOMAIN + "/aliases")
+                .then()
+                    .contentType(ContentType.JSON)
+                    .statusCode(HttpStatus.OK_200)
+                    .body("source", containsInAnyOrder(ALIAS_DOMAIN));
+            }
+
+            @Test
+            void getAliasesShouldNotReturnDeletedAliases() {
+                with().put(DOMAIN);
+                with().put(ALIAS_DOMAIN);
+
+                with().put(DOMAIN + "/aliases/" + ALIAS_DOMAIN);
+                with().delete(DOMAIN + "/aliases/" + ALIAS_DOMAIN);
+
+                when()
+                    .get(DOMAIN + "/aliases")
+                .then()
+                    .contentType(ContentType.JSON)
+                    .statusCode(HttpStatus.OK_200)
+                    .body(".", hasSize(0));
+            }
+
+            @Test
+            void deleteShouldReturnNotFoundWhenAliasDomainDoesNotExist() {
+                with().put(DOMAIN);
+
+                when()
+                    .delete(DOMAIN + "/aliases/" + ALIAS_DOMAIN)
+                .then()
+                    .contentType(ContentType.JSON)
+                    .statusCode(HttpStatus.NOT_FOUND_404)
+                    .body("statusCode", is(HttpStatus.NOT_FOUND_404))
+                    .body("type", is("InvalidArgument"))
+                    .body("message", is("The domain list does not contain: " + ALIAS_DOMAIN));
+            }
+
+            @Test
+            void putShouldReturnNotFoundWhenAliasDomainDoesNotExist() {
+                with().put(DOMAIN);
+
+                when()
+                    .put(DOMAIN + "/aliases/" + ALIAS_DOMAIN)
+                .then()
+                    .contentType(ContentType.JSON)
+                    .statusCode(HttpStatus.NOT_FOUND_404)
+                    .body("statusCode", is(HttpStatus.NOT_FOUND_404))
+                    .body("type", is("InvalidArgument"))
+                    .body("message", is("The domain list does not contain: " + ALIAS_DOMAIN));
+            }
+
+            @Test
+            void putShouldNotFailOnExternalDomainAlias() {
+                with().put(DOMAIN);
+
+                when()
+                    .put(EXTERNAL_DOMAIN + "/aliases/" + DOMAIN).prettyPeek()
+                .then()
+                    .contentType(ContentType.JSON)
+                    .statusCode(HttpStatus.NO_CONTENT_204);
+            }
+
+            @Test
+            void deleteShouldNotFailOnExternalDomainDestinationForAnAlias() {
+                with().put(DOMAIN);
+
+                when()
+                    .delete(EXTERNAL_DOMAIN + "/aliases/" + DOMAIN)
+                .then()
+                    .contentType(ContentType.JSON)
+                    .statusCode(HttpStatus.NO_CONTENT_204);
+            }
+
+            @Test
+            void getAliasesShouldListAliasesForExternalDomains() {
+                with().put(DOMAIN);
+
+                with().put(EXTERNAL_DOMAIN + "/aliases/" + DOMAIN);
+
+                when()
+                    .get(EXTERNAL_DOMAIN + "/aliases")
+                .then()
+                    .contentType(ContentType.JSON)
+                    .statusCode(HttpStatus.OK_200)
+                    .body("source", containsInAnyOrder(DOMAIN));
+            }
+
+            @Test
+            void deleteShouldRemoveExternalDomainAlias() {
+                with().put(DOMAIN);
+
+                with().put(EXTERNAL_DOMAIN + "/aliases/" + DOMAIN);
+
+                with().delete(EXTERNAL_DOMAIN + "/aliases/" + DOMAIN);
+
+                when()
+                    .get(DOMAIN + "/aliases")
+                .then()
+                    .contentType(ContentType.JSON)
+                    .statusCode(HttpStatus.OK_200)
+                    .body("source", hasSize(0));
+            }
+
+            @Test
+            void putShouldReturnBadRequestWhenDestinationDomainIsInvalid() {
+                when()
+                    .put("invalid@domain/aliases/" + ALIAS_DOMAIN)
+                .then()
+                    .contentType(ContentType.JSON)
+                    .statusCode(HttpStatus.BAD_REQUEST_400)
+                    .body("statusCode", is(HttpStatus.BAD_REQUEST_400))
+                    .body("type", is("InvalidArgument"))
+                    .body("message", is("Invalid request for domain creation invalid@domain"));
+            }
+
+            @Test
+            void putShouldReturnBadRequestWhenSourceDomainIsInvalid() {
+                when()
+                    .put("domain/aliases/invalid@alias.domain")
+                .then()
+                    .contentType(ContentType.JSON)
+                    .statusCode(HttpStatus.BAD_REQUEST_400)
+                    .body("statusCode", is(HttpStatus.BAD_REQUEST_400))
+                    .body("type", is("InvalidArgument"))
+                    .body("message", is("Invalid request for domain creation invalid@alias.domain"));
+            }
+
+            @Test
+            void deleteShouldReturnBadRequestWhenDestinationDomainIsInvalid() {
+                when()
+                    .delete("invalid@domain/aliases/" + ALIAS_DOMAIN)
+                .then()
+                    .contentType(ContentType.JSON)
+                    .statusCode(HttpStatus.BAD_REQUEST_400)
+                    .body("statusCode", is(HttpStatus.BAD_REQUEST_400))
+                    .body("type", is("InvalidArgument"))
+                    .body("message", is("Invalid request for domain creation invalid@domain"));
+            }
+
+            @Test
+            void deleteShouldReturnBadRequestWhenSourceDomainIsInvalid() {
+                when()
+                    .delete("domain/aliases/invalid@alias.domain")
+                .then()
+                    .contentType(ContentType.JSON)
+                    .statusCode(HttpStatus.BAD_REQUEST_400)
+                    .body("statusCode", is(HttpStatus.BAD_REQUEST_400))
+                    .body("type", is("InvalidArgument"))
+                    .body("message", is("Invalid request for domain creation invalid@alias.domain"));
+            }
+
+            @Test
+            void getAliasesShouldReturnBadRequestWhenDomainIsInvalid() {
+                when()
+                    .get("invalid@domain/aliases")
+                .then()
+                    .contentType(ContentType.JSON)
+                    .statusCode(HttpStatus.BAD_REQUEST_400)
+                    .body("statusCode", is(HttpStatus.BAD_REQUEST_400))
+                    .body("type", is("InvalidArgument"))
+                    .body("message", is("Invalid request for domain creation invalid@domain"));
+            }
+        }
+
     }
 
     @Nested


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