JAMES-1759 WebAdminServer should allow CRUD operations on domains
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/6053d805 Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/6053d805 Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/6053d805 Branch: refs/heads/master Commit: 6053d8056b91322988d8e70d57c79c9a2d6baa90 Parents: ff64e6d Author: Benoit Tellier <[email protected]> Authored: Thu Jun 16 19:13:13 2016 +0700 Committer: Benoit Tellier <[email protected]> Committed: Wed Jun 22 15:32:24 2016 +0700 ---------------------------------------------------------------------- .../james/webadmin/routes/DomainRoutes.java | 118 +++++++ .../james/webadmin/utils/JsonTransformer.java | 40 +++ .../james/webadmin/routes/DomainRoutesTest.java | 318 +++++++++++++++++++ 3 files changed, 476 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/6053d805/server/protocols/webadmin/src/main/java/org/apache/james/webadmin/routes/DomainRoutes.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/src/main/java/org/apache/james/webadmin/routes/DomainRoutes.java b/server/protocols/webadmin/src/main/java/org/apache/james/webadmin/routes/DomainRoutes.java new file mode 100644 index 0000000..bbfa522 --- /dev/null +++ b/server/protocols/webadmin/src/main/java/org/apache/james/webadmin/routes/DomainRoutes.java @@ -0,0 +1,118 @@ +/**************************************************************** + * 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 javax.inject.Inject; + +import org.apache.james.domainlist.api.DomainList; +import org.apache.james.domainlist.api.DomainListException; +import org.apache.james.webadmin.Routes; +import org.apache.james.webadmin.utils.JsonTransformer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; + +import spark.Request; +import spark.Response; +import spark.Service; + +public class DomainRoutes implements Routes { + + private static final String DOMAIN_NAME = ":domainName"; + private static final String EMPTY_BODY = ""; + private static final Logger LOGGER = LoggerFactory.getLogger(DomainRoutes.class); + + 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 final DomainList domainList; + private final JsonTransformer jsonTransformer; + + @Inject + public DomainRoutes(DomainList domainList, JsonTransformer jsonTransformer) { + this.domainList = domainList; + this.jsonTransformer = jsonTransformer; + } + + @Override + public void define(Service service) { + service.get(DOMAINS, + (request, response) -> domainList.getDomains(), + jsonTransformer); + + service.get(SPECIFIC_DOMAIN, this::exists); + + service.put(SPECIFIC_DOMAIN, this::addDomain); + + service.delete(SPECIFIC_DOMAIN, this::removeDomain); + } + + private String removeDomain(Request request, Response response) { + try { + String domain = request.params(DOMAIN_NAME); + removeDomain(domain); + } catch (DomainListException e) { + LOGGER.info("{} did not exists", request.params(DOMAIN_NAME)); + } + response.status(204); + return EMPTY_BODY; + } + + private void removeDomain(String domain) throws DomainListException { + Preconditions.checkArgument(!Strings.isNullOrEmpty(domain)); + domainList.removeDomain(domain); + } + + private String addDomain(Request request, Response response) { + try { + addDomain(request.params(DOMAIN_NAME)); + response.status(204); + } catch (DomainListException e) { + LOGGER.info("{} already exists", request.params(DOMAIN_NAME)); + response.status(204); + } catch (IllegalArgumentException e) { + LOGGER.info("Invalid request for domain creation"); + response.status(400); + } + return EMPTY_BODY; + } + + private void addDomain(String domain) throws DomainListException { + Preconditions.checkArgument(!Strings.isNullOrEmpty(domain)); + Preconditions.checkArgument(!domain.contains("@")); + Preconditions.checkArgument(domain.length() < MAXIMUM_DOMAIN_SIZE); + domainList.addDomain(domain); + } + + private String exists(Request request, Response response) throws DomainListException { + if (!domainList.containsDomain(request.params(DOMAIN_NAME))) { + response.status(404); + } else { + response.status(204); + } + return EMPTY_BODY; + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/6053d805/server/protocols/webadmin/src/main/java/org/apache/james/webadmin/utils/JsonTransformer.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/src/main/java/org/apache/james/webadmin/utils/JsonTransformer.java b/server/protocols/webadmin/src/main/java/org/apache/james/webadmin/utils/JsonTransformer.java new file mode 100644 index 0000000..c1a17f9 --- /dev/null +++ b/server/protocols/webadmin/src/main/java/org/apache/james/webadmin/utils/JsonTransformer.java @@ -0,0 +1,40 @@ +/**************************************************************** + * 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.utils; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; + +import spark.ResponseTransformer; + +public class JsonTransformer implements ResponseTransformer { + + private final ObjectMapper objectMapper; + + public JsonTransformer() { + objectMapper = new ObjectMapper(); + objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + } + + @Override + public String render(Object o) throws Exception { + return objectMapper.writeValueAsString(o); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/6053d805/server/protocols/webadmin/src/test/java/org/apache/james/webadmin/routes/DomainRoutesTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/src/test/java/org/apache/james/webadmin/routes/DomainRoutesTest.java b/server/protocols/webadmin/src/test/java/org/apache/james/webadmin/routes/DomainRoutesTest.java new file mode 100644 index 0000000..c401933 --- /dev/null +++ b/server/protocols/webadmin/src/test/java/org/apache/james/webadmin/routes/DomainRoutesTest.java @@ -0,0 +1,318 @@ +/**************************************************************** + * 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.RestAssured.with; +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.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.is; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.net.InetAddress; + +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.webadmin.WebAdminServer; +import org.apache.james.webadmin.utils.JsonTransformer; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Charsets; +import com.jayway.restassured.RestAssured; +import com.jayway.restassured.parsing.Parser; + +import de.bechte.junit.runners.context.HierarchicalContextRunner; + +@RunWith(HierarchicalContextRunner.class) +public class DomainRoutesTest { + + private static final Logger LOGGER = LoggerFactory.getLogger(DomainRoutesTest.class); + public static final String DOMAIN = "domain"; + + private WebAdminServer webAdminServer; + + private void createServer(DomainList domainList) throws Exception { + webAdminServer = new WebAdminServer(new DomainRoutes(domainList, new JsonTransformer())); + webAdminServer.configure(NO_CONFIGURATION); + webAdminServer.await(); + + RestAssured.port = webAdminServer.getPort().toInt(); + RestAssured.config = newConfig().encoderConfig(encoderConfig().defaultContentCharset(Charsets.UTF_8)); + RestAssured.defaultParser = Parser.JSON; + RestAssured.basePath = DomainRoutes.DOMAINS; + } + + @After + public void stop() { + webAdminServer.destroy(); + } + + public class NormalBehaviour { + + @Before + public void setUp() throws Exception { + DNSService dnsService = mock(DNSService.class); + when(dnsService.getHostName(any())).thenReturn("localhost"); + when(dnsService.getLocalHost()).thenReturn(InetAddress.getByName("localhost")); + + MemoryDomainList domainList = new MemoryDomainList(); + domainList.setDNSService(dnsService); + domainList.setLog(LOGGER); + domainList.setAutoDetectIP(false); + createServer(domainList); + } + + @Test + public void getDomainsShouldBeEmptyByDefault() { + given() + .get() + .then() + .statusCode(200) + .body(is("[]")); + } + + @Test + public void putShouldReturnErrorWhenUsedWithEmptyDomain() { + given() + .put(SEPARATOR) + .then() + .statusCode(404); + } + + @Test + public void deleteShouldReturnErrorWhenUsedWithEmptyDomain() { + given() + .delete(SEPARATOR) + .then() + .statusCode(404); + } + + @Test + public void putShouldBeOk() { + given() + .put(DOMAIN) + .then() + .statusCode(204); + } + + @Test + public void getDomainsShouldDisplayAddedDomains() { + with() + .put(DOMAIN); + + when() + .get() + .then() + .statusCode(200) + .body(containsString(DOMAIN)); + } + + @Test + public void putShouldReturnUserErrorWhenNameContainsAT() { + when() + .put(DOMAIN + "@" + DOMAIN) + .then() + .statusCode(400); + } + + @Test + public void putShouldReturnUserErrorWhenNameContainsUrlSeparator() { + when() + .put(DOMAIN + "/" + DOMAIN) + .then() + .statusCode(404); + } + + @Test + public void putShouldReturnUserErrorWhenNameIsTooLong() { + when() + .put(DOMAIN + "0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789." + + "0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789." + + "0123456789.0123456789.0123456789.") + .then() + .statusCode(400); + } + + @Test + public void putShouldWorkOnTheSecondTimeForAGivenValue() { + with() + .put(DOMAIN); + + when() + .put(DOMAIN) + .then() + .statusCode(204); + } + + @Test + public void deleteShouldRemoveTheGivenDomain() { + with() + .put(DOMAIN); + + when() + .delete(DOMAIN) + .then() + .statusCode(204); + + when() + .get() + .then() + .statusCode(200) + .body(is("[]")); + } + + @Test + public void deleteShouldBeOkWhenTheDomainIsNotPresent() { + given() + .delete(DOMAIN) + .then() + .statusCode(204); + } + + @Test + public void getDomainShouldReturnOkWhenTheDomainIsPresent() { + with() + .put(DOMAIN); + + when() + .get(DOMAIN) + .then() + .statusCode(204); + } + + @Test + public void getDomainShouldReturnNotFoundWhenTheDomainIsAbsent() { + given() + .get(DOMAIN) + .then() + .statusCode(404); + } + + } + + public class ExceptionHandling { + + private DomainList domainList; + private String domain; + + @Before + public void setUp() throws Exception { + domainList = mock(DomainList.class); + createServer(domainList); + domain = "domain"; + } + + @Test + public void deleteShouldReturnErrorOnUnknownException() throws Exception { + doThrow(new RuntimeException()).when(domainList).removeDomain(domain); + + when() + .delete(DOMAIN) + .then() + .statusCode(500); + } + + @Test + public void putShouldReturnErrorOnUnknownException() throws Exception { + doThrow(new RuntimeException()).when(domainList).addDomain(domain); + + when() + .put(DOMAIN) + .then() + .statusCode(500); + } + + @Test + public void getDomainShouldReturnErrorOnUnknownException() throws Exception { + when(domainList.containsDomain(domain)).thenThrow(new RuntimeException()); + + when() + .get(DOMAIN) + .then() + .statusCode(500); + } + + @Test + public void getDomainsShouldReturnErrorOnUnknownException() throws Exception { + when(domainList.getDomains()).thenThrow(new RuntimeException()); + + when() + .get() + .then() + .statusCode(500); + } + + @Test + public void deleteShouldReturnOkWhenDomainListException() throws Exception { + doThrow(new DomainListException("message")).when(domainList).removeDomain(domain); + + when() + .delete(DOMAIN) + .then() + .statusCode(204); + } + + @Test + public void putShouldReturnOkWhenDomainListException() throws Exception { + doThrow(new DomainListException("message")).when(domainList).addDomain(domain); + + when() + .put(DOMAIN) + .then() + .statusCode(204); + } + + @Test + public void getDomainShouldReturnErrorOnDomainListException() throws Exception { + when(domainList.containsDomain(domain)).thenThrow(new DomainListException("message")); + + when() + .get(DOMAIN) + .then() + .statusCode(500); + } + + @Test + public void getDomainsShouldReturnErrorOnDomainListException() throws Exception { + when(domainList.getDomains()).thenThrow(new DomainListException("message")); + + when() + .get() + .then() + .statusCode(500); + } + + } + +} --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
