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 2020/04/13 02:53:37 UTC

[james-project] 10/13: JAMES-2888: Remove JMAPRoutes, correct CoreEcho method and test, ordering pom, remove unused [no review] commit. Just unit test, not integration yet.

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 d72dba483f54fd540fcd1a722d62b982229e8c9c
Author: ducnv <du...@gmail.com>
AuthorDate: Wed Apr 1 08:59:18 2020 +0700

    JAMES-2888: Remove JMAPRoutes, correct CoreEcho method and test, ordering pom, remove unused [no review] commit.
    Just unit test, not integration yet.
---
 pom.xml                                            |   5 +
 server/protocols/jmap-rfc-8621/pom.xml             |  42 +++---
 .../org/apache/james/jmap/json/Serializer.scala    |   5 +
 .../ResponseObject.scala => method/CoreEcho.scala} |  14 +-
 .../ResponseObject.scala => method/Method.scala}   |  13 +-
 .../apache/james/jmap/model/ResponseObject.scala   |   5 +
 .../apache/james/jmap/routes/JMAPApiRoutes.scala   |  99 +++++++++-----
 .../scala/org/apache/james/jmap/json/Fixture.scala |  13 +-
 .../jmap/json/RequestObjectSerializationTest.scala |  12 +-
 .../json/ResponseObjectSerializationTest.scala     |   8 +-
 .../apache/james/jmap/method/CoreEchoTest.scala    |   3 +-
 .../james/jmap/routes/JMAPApiRoutesTest.scala      | 152 +++++++++++++++++++++
 12 files changed, 299 insertions(+), 72 deletions(-)

diff --git a/pom.xml b/pom.xml
index a30cb9b..d39bd79 100644
--- a/pom.xml
+++ b/pom.xml
@@ -664,6 +664,11 @@
                 <scope>import</scope>
             </dependency>
             <dependency>
+                <groupId>io.projectreactor</groupId>
+                <artifactId>reactor-scala-extensions_${scala.base}</artifactId>
+                <version>0.5.1</version>
+            </dependency>
+            <dependency>
                 <groupId>${james.groupId}</groupId>
                 <artifactId>apache-james-backends-cassandra</artifactId>
                 <version>${project.version}</version>
diff --git a/server/protocols/jmap-rfc-8621/pom.xml b/server/protocols/jmap-rfc-8621/pom.xml
index 79dfe41..bb6a83b 100644
--- a/server/protocols/jmap-rfc-8621/pom.xml
+++ b/server/protocols/jmap-rfc-8621/pom.xml
@@ -33,6 +33,28 @@
 
     <dependencies>
         <dependency>
+            <groupId>com.typesafe.play</groupId>
+            <artifactId>play-json_${scala.base}</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>eu.timepit</groupId>
+            <artifactId>refined_${scala.base}</artifactId>
+            <version>0.9.13</version>
+        </dependency>
+        <dependency>
+            <groupId>io.rest-assured</groupId>
+            <artifactId>rest-assured</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.projectreactor.netty</groupId>
+            <artifactId>reactor-netty</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.projectreactor</groupId>
+            <artifactId>reactor-scala-extensions_${scala.base}</artifactId>
+        </dependency>
+        <dependency>
             <groupId>${james.groupId}</groupId>
             <artifactId>james-core</artifactId>
         </dependency>
@@ -56,25 +78,6 @@
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>com.typesafe.play</groupId>
-            <artifactId>play-json_${scala.base}</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>eu.timepit</groupId>
-            <artifactId>refined_${scala.base}</artifactId>
-            <version>0.9.13</version>
-        </dependency>
-        <dependency>
-            <groupId>io.projectreactor</groupId>
-            <artifactId>reactor-scala-extensions_${scala.base}</artifactId>
-            <version>0.5.1</version>
-        </dependency>
-        <dependency>
-            <groupId>io.rest-assured</groupId>
-            <artifactId>rest-assured</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
             <groupId>org.mockito</groupId>
             <artifactId>mockito-core</artifactId>
             <scope>test</scope>
@@ -94,7 +97,6 @@
             <artifactId>jcl-over-slf4j</artifactId>
         </dependency>
     </dependencies>
-
     <build>
         <plugins>
             <plugin>
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala
index 2189af9..173a82d 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala
@@ -19,6 +19,7 @@
 
 package org.apache.james.jmap.json
 
+import java.io.InputStream
 import java.net.URL
 
 import org.apache.james.core.Username
@@ -141,6 +142,10 @@ class Serializer {
     Json.parse(input).validate[RequestObject]
   }
 
+  def deserializeRequestObject(input: InputStream): JsResult[RequestObject] = {
+    Json.parse(input).validate[RequestObject]
+  }
+
   def deserializeResponseObject(input: String): JsResult[ResponseObject] = {
     Json.parse(input).validate[ResponseObject]
   }
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/ResponseObject.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/CoreEcho.scala
similarity index 71%
copy from server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/ResponseObject.scala
copy to server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/CoreEcho.scala
index a50d3a2..efeb0e7 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/ResponseObject.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/CoreEcho.scala
@@ -16,9 +16,17 @@
  * specific language governing permissions and limitations      *
  * under the License.                                           *
  * ***************************************************************/
+package org.apache.james.jmap.method
 
-package org.apache.james.jmap.model
 
-import org.apache.james.jmap.model.State.State
+import eu.timepit.refined.auto._
+import org.apache.james.jmap.model.Invocation
+import org.apache.james.jmap.model.Invocation.MethodName
+import org.reactivestreams.Publisher
+import reactor.core.scala.publisher.SMono
 
-case class ResponseObject(sessionState: State, methodResponses: Seq[Invocation])
+class CoreEcho extends Method {
+  override val methodName = MethodName("Core/echo")
+
+  override def process(invocation: Invocation): Publisher[Invocation] = SMono.just(invocation)
+}
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/ResponseObject.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/Method.scala
similarity index 78%
copy from server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/ResponseObject.scala
copy to server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/Method.scala
index a50d3a2..7a89b84 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/ResponseObject.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/Method.scala
@@ -16,9 +16,16 @@
  * specific language governing permissions and limitations      *
  * under the License.                                           *
  * ***************************************************************/
+package org.apache.james.jmap.method
 
-package org.apache.james.jmap.model
 
-import org.apache.james.jmap.model.State.State
+import org.apache.james.jmap.model.Invocation
+import org.apache.james.jmap.model.Invocation.MethodName
+import org.reactivestreams.Publisher
+
+trait Method {
+  val methodName: MethodName
+
+  def process(invocation: Invocation): Publisher[Invocation]
+}
 
-case class ResponseObject(sessionState: State, methodResponses: Seq[Invocation])
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/ResponseObject.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/ResponseObject.scala
index a50d3a2..783801b 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/ResponseObject.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/ResponseObject.scala
@@ -19,6 +19,11 @@
 
 package org.apache.james.jmap.model
 
+import eu.timepit.refined.auto._
 import org.apache.james.jmap.model.State.State
 
 case class ResponseObject(sessionState: State, methodResponses: Seq[Invocation])
+
+object ResponseObject {
+  val SESSION_STATE: State = "75128aab4b1b"
+}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala
index ddfe2fe..8336487 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala
@@ -18,52 +18,87 @@
  * ***************************************************************/
 package org.apache.james.jmap.routes
 
+import java.io.InputStream
+import java.nio.charset.StandardCharsets
+import java.util.stream
+import java.util.stream.Stream
+
 import eu.timepit.refined.auto._
+import io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE
+import io.netty.handler.codec.http.HttpMethod
+import io.netty.handler.codec.http.HttpResponseStatus.OK
+import org.apache.james.jmap.HttpConstants.JSON_CONTENT_TYPE
+import org.apache.james.jmap.JMAPUrls.JMAP
 import org.apache.james.jmap.json.Serializer
 import org.apache.james.jmap.method.CoreEcho
 import org.apache.james.jmap.model.Invocation.{Arguments, MethodName}
 import org.apache.james.jmap.model.{Invocation, RequestObject, ResponseObject}
-import org.reactivestreams.Publisher
+import org.apache.james.jmap.{Endpoint, JMAPRoute, JMAPRoutes}
+import org.slf4j.{Logger, LoggerFactory}
 import play.api.libs.json.{JsError, JsSuccess, Json}
-import reactor.core.scala.publisher.SMono
+import reactor.core.publisher.Mono
+import reactor.core.scala.publisher.{SFlux, SMono}
+import reactor.core.scheduler.Schedulers
 import reactor.netty.http.server.{HttpServerRequest, HttpServerResponse}
 
-object JMAPApiRoutes {
-  private val ECHO_METHOD = new CoreEcho()
-}
+class JMAPApiRoutes extends JMAPRoutes {
+  override def logger(): Logger = LoggerFactory.getLogger(getClass)
+
+  private val coreEcho = new CoreEcho
 
-class JMAPApiRoutes {
-  private val echoMethod = JMAPApiRoutes.ECHO_METHOD
+  override def routes(): stream.Stream[JMAPRoute] = Stream.of(
+    JMAPRoute.builder
+      .endpoint(new Endpoint(HttpMethod.POST, JMAP))
+      .action(this.post)
+      .corsHeaders,
+    JMAPRoute.builder
+      .endpoint(new Endpoint(HttpMethod.OPTIONS, JMAP))
+      .action(JMAPRoutes.CORS_CONTROL)
+      .corsHeaders())
 
-  def post(httpServerRequest: HttpServerRequest, httpServerResponse: HttpServerResponse): SMono[Void] = {
-    SMono.fromPublisher(extractRequestObject(httpServerRequest))
-      .flatMap(this.process)
-      .doOnError(e => new RuntimeException(e.getMessage))
+  private def post(httpServerRequest: HttpServerRequest, httpServerResponse: HttpServerResponse): Mono[Void] =
+    this.requestAsJsonStream(httpServerRequest)
+      .flatMap(requestObject => this.process(requestObject, httpServerResponse))
+      .onErrorResume(throwable => SMono.fromPublisher(handleInternalError(httpServerResponse, throwable)))
+      .subscribeOn(Schedulers.elastic)
+      .asJava()
       .`then`()
+
+  private def requestAsJsonStream(httpServerRequest: HttpServerRequest): SMono[RequestObject] = {
+    SMono.fromPublisher(httpServerRequest
+      .receive()
+      .aggregate()
+      .asInputStream())
+      .flatMap(this.parseRequestObject)
   }
 
-  private def process(requestObject: RequestObject): SMono[ResponseObject] = {
-    SMono.just(
-      requestObject.methodCalls.map((invocation: Invocation) =>
-        invocation.methodName match {
-          case echoMethod.methodName => echoMethod.process(invocation)
-          case _ => SMono.just(new Invocation(
-            MethodName("error"),
-            Arguments(Json.obj("type" -> "Not implemented")),
-            invocation.methodCallId))
-        }
+  private def parseRequestObject(inputStream: InputStream): SMono[RequestObject] =
+    new Serializer().deserializeRequestObject(inputStream) match {
+      case JsSuccess(requestObject, _) => SMono.just(requestObject)
+      case JsError(errors) => SMono.raiseError(new RuntimeException(errors.toString()))
+    }
+
+  private def process(requestObject: RequestObject, httpServerResponse: HttpServerResponse): SMono[Void] =
+    requestObject
+      .methodCalls
+      .map(this.processMethodWithMatchName)
+      .foldLeft(SFlux.empty[Invocation]) { (flux: SFlux[Invocation], mono: SMono[Invocation]) => flux.mergeWith(mono) }
+      .collectSeq()
+      .flatMap((invocations: Seq[Invocation]) =>
+        SMono.fromPublisher(httpServerResponse.status(OK)
+          .header(CONTENT_TYPE, JSON_CONTENT_TYPE)
+          .sendString(
+            SMono.fromCallable(() =>
+              new Serializer().serialize(ResponseObject(ResponseObject.SESSION_STATE, invocations)).toString()),
+            StandardCharsets.UTF_8
+          ).`then`())
       )
-    ).flatMap((invocations: Seq[Invocation]) => SMono.just(ResponseObject(ResponseObject.SESSION_STATE, invocations)))
-  }
 
-  private def extractRequestObject(httpServerRequest: HttpServerRequest): Publisher[RequestObject] = {
-    httpServerRequest
-      .receive()
-      .asInputStream()
-      .flatMap(inputStream => new Serializer().deserializeRequestObject(inputStream) match {
-        case JsSuccess(requestObject, _) => SMono.just(new ResponseObject(ResponseObject.SESSION_STATE, requestObject.methodCalls))
-        case JsError(errors) => SMono.raiseError(new RuntimeException(errors.toString()))
-      })
+  private def processMethodWithMatchName(invocation: Invocation): SMono[Invocation] = invocation.methodName match {
+    case coreEcho.methodName => SMono.fromPublisher(coreEcho.process(invocation))
+    case _ => SMono.just(new Invocation(
+      MethodName("error"),
+      Arguments(Json.obj("type" -> "Not implemented")),
+      invocation.methodCallId))
   }
 }
-
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/Fixture.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/Fixture.scala
index bba26fa..e249535 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/Fixture.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/Fixture.scala
@@ -23,7 +23,7 @@ import eu.timepit.refined.auto._
 import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
 import org.apache.james.jmap.model.Id.Id
 import org.apache.james.jmap.model.Invocation.{Arguments, MethodCallId, MethodName}
-import org.apache.james.jmap.model.{ClientId, CreatedIds, Invocation, ServerId}
+import org.apache.james.jmap.model.{ClientId, CreatedIds, Invocation, ResponseObject, ServerId}
 import play.api.libs.json.Json
 
 object Fixture {
@@ -32,12 +32,19 @@ object Fixture {
   val coreIdentifier: CapabilityIdentifier = "urn:ietf:params:jmap:core"
   val mailIdentifier: CapabilityIdentifier = "urn:ietf:params:jmap:mail"
   val invocation1: Invocation = Invocation(
-    methodName = MethodName("Core/echo1"),
+    methodName = MethodName("Core/echo"),
     arguments = Arguments(Json.obj("arg1" -> "arg1data", "arg2" -> "arg2data")),
     methodCallId = MethodCallId("c1"))
   val invocation2: Invocation = Invocation(
-    methodName = MethodName("Core/echo2"),
+    methodName = MethodName("Core/echo"),
     arguments = Arguments(Json.obj("arg3" -> "arg3data", "arg4" -> "arg4data")),
     methodCallId = MethodCallId("c2")
   )
+  val unsupportedInvocation: Invocation = Invocation(
+    methodName = MethodName("error"),
+    arguments = Arguments(Json.obj("type" -> "Not implemented")),
+    methodCallId = MethodCallId("notsupport"))
+  val responseObject1: ResponseObject = ResponseObject(ResponseObject.SESSION_STATE, Seq(invocation1))
+  val responseObject2: ResponseObject = ResponseObject(ResponseObject.SESSION_STATE, Seq(invocation2))
+  val responseObjectWithUnsupportedMethod: ResponseObject = ResponseObject(ResponseObject.SESSION_STATE, Seq(invocation1, unsupportedInvocation))
 }
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/RequestObjectSerializationTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/RequestObjectSerializationTest.scala
index 173c026..4d1c5bd 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/RequestObjectSerializationTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/RequestObjectSerializationTest.scala
@@ -39,7 +39,7 @@ class RequestObjectSerializationTest extends AnyWordSpec with Matchers {
             |{
             |  "using": [ "urn:ietf:params:jmap:core"],
             |  "methodCalls": [
-            |    [ "Core/echo1", {
+            |    [ "Core/echo", {
             |      "arg1": "arg1data",
             |      "arg2": "arg2data"
             |    }, "c1" ]
@@ -60,7 +60,7 @@ class RequestObjectSerializationTest extends AnyWordSpec with Matchers {
             |{
             |  "using": [ "urn:ietf:params:jmap:core"],
             |  "methodCalls": [
-            |    [ "Core/echo1", {
+            |    [ "Core/echo", {
             |      "arg1": "arg1data",
             |      "arg2": "arg2data"
             |    }, "c1" ]
@@ -82,11 +82,11 @@ class RequestObjectSerializationTest extends AnyWordSpec with Matchers {
             |{
             |  "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
             |  "methodCalls": [
-            |    [ "Core/echo1", {
+            |    [ "Core/echo", {
             |      "arg1": "arg1data",
             |      "arg2": "arg2data"
             |    }, "c1" ],
-            |    [ "Core/echo2", {
+            |    [ "Core/echo", {
             |      "arg3": "arg3data",
             |      "arg4": "arg4data"
             |    }, "c2" ]
@@ -109,7 +109,7 @@ class RequestObjectSerializationTest extends AnyWordSpec with Matchers {
           |{
           |  "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
           |  "methodCalls": [
-          |    [ "Core/echo1", {
+          |    [ "Core/echo", {
           |      "arg1": "arg1data",
           |      "arg2": "arg2data"
           |    }, "c1" ]
@@ -132,7 +132,7 @@ class RequestObjectSerializationTest extends AnyWordSpec with Matchers {
           |{
           |  "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
           |  "methodCalls": [
-          |    [ "Core/echo1", {
+          |    [ "Core/echo", {
           |      "arg1": "arg1data",
           |      "arg2": "arg2data"
           |    }, "c1" ]
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/ResponseObjectSerializationTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/ResponseObjectSerializationTest.scala
index 6dbbd8e..11c2504 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/ResponseObjectSerializationTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/ResponseObjectSerializationTest.scala
@@ -37,7 +37,7 @@ class ResponseObjectSerializationTest extends AnyWordSpec with Matchers {
         """
           |{
           |  "methodResponses": [
-          |    [ "Core/echo1", {
+          |    [ "Core/echo", {
           |      "arg1": "arg1data",
           |      "arg2": "arg2data"
           |    }, "c1" ]
@@ -57,11 +57,11 @@ class ResponseObjectSerializationTest extends AnyWordSpec with Matchers {
           |{
           |  "sessionState": "75128aab4b1b",
           |  "methodResponses": [
-          |    [ "Core/echo1", {
+          |    [ "Core/echo", {
           |      "arg1": "arg1data",
           |      "arg2": "arg2data"
           |    }, "c1" ],
-          |    [ "Core/echo2", {
+          |    [ "Core/echo", {
           |      "arg3": "arg3data",
           |      "arg4": "arg4data"
           |    }, "c2" ]
@@ -82,7 +82,7 @@ class ResponseObjectSerializationTest extends AnyWordSpec with Matchers {
           |{
           |  "sessionState": "75128aab4b1b",
           |  "methodResponses": [
-          |    [ "Core/echo1", {
+          |    [ "Core/echo", {
           |      "arg1": "arg1data",
           |      "arg2": "arg2data"
           |    }, "c1" ]
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/method/CoreEchoTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/method/CoreEchoTest.scala
index b723321..2d78e5f 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/method/CoreEchoTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/method/CoreEchoTest.scala
@@ -22,6 +22,7 @@ import org.apache.james.jmap.json.Fixture.{invocation1, invocation2}
 import org.apache.james.jmap.model.Invocation
 import org.scalatest.matchers.should.Matchers
 import org.scalatest.wordspec.AnyWordSpec
+import reactor.core.scala.publisher.SMono
 
 class CoreEchoTest extends AnyWordSpec with Matchers {
   private val echoMethod: CoreEcho = new CoreEcho()
@@ -38,7 +39,7 @@ class CoreEchoTest extends AnyWordSpec with Matchers {
       "success and not return anything else different than the original invocation" in {
         val wrongExpected: Invocation = invocation2
         val dataResponse = SMono.fromPublisher(echoMethod.process(invocation1)).block()
-
+        
         dataResponse should not be(wrongExpected)
       }
     }
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala
new file mode 100644
index 0000000..55bf125
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala
@@ -0,0 +1,152 @@
+/** **************************************************************
+ * 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.jmap.routes
+
+import java.nio.charset.StandardCharsets
+
+import com.google.common.collect.ImmutableSet
+import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
+import io.restassured.RestAssured
+import io.restassured.builder.RequestSpecBuilder
+import io.restassured.config.EncoderConfig.encoderConfig
+import io.restassured.config.RestAssuredConfig.newConfig
+import io.restassured.http.ContentType
+import org.apache.http.HttpStatus
+import org.apache.james.jmap.JMAPUrls.JMAP
+import org.apache.james.jmap.json.Fixture._
+import org.apache.james.jmap.json.Serializer
+import org.apache.james.jmap.model.RequestObject
+import org.apache.james.jmap.{JMAPConfiguration, JMAPRoutesHandler, JMAPServer, Version}
+import org.scalatest.BeforeAndAfter
+import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+class JMAPApiRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
+
+  private val TEST_CONFIGURATION: JMAPConfiguration = JMAPConfiguration.builder().enable().randomPort().build()
+  private val ACCEPT_JMAP_VERSION_HEADER = "application/json; jmapVersion="
+  private val ACCEPT_DRAFT_VERSION_HEADER = ACCEPT_JMAP_VERSION_HEADER + Version.DRAFT.getVersion
+  private val ACCEPT_RFC8621_VERSION_HEADER = ACCEPT_JMAP_VERSION_HEADER + Version.RFC8621.getVersion
+
+  private val JMAP_API_ROUTE: JMAPApiRoutes = new JMAPApiRoutes()
+  private val ROUTES_HANDLER: ImmutableSet[JMAPRoutesHandler] = ImmutableSet.of(new JMAPRoutesHandler(Version.RFC8621, JMAP_API_ROUTE))
+
+  private val REQUEST_OBJECT: String =
+    new Serializer().serialize(RequestObject(Seq(coreIdentifier), Seq(invocation1))).toString()
+
+  private val REQUEST_OBJECT_WITH_UNSUPPORTED_METHOD: String =
+    new Serializer().serialize(RequestObject(Seq(coreIdentifier), Seq(invocation1, unsupportedInvocation))).toString()
+
+  private val RESPONSE_OBJECT: String = new Serializer().serialize(responseObject1).toString()
+  private val RESPONSE_OBJECT_WITH_UNSUPPORTED_METHOD: String = new Serializer().serialize(responseObjectWithUnsupportedMethod).toString()
+
+  var jmapServer: JMAPServer = _
+
+  before {
+    jmapServer = new JMAPServer(TEST_CONFIGURATION, ROUTES_HANDLER)
+    jmapServer.start()
+
+    RestAssured.requestSpecification = new RequestSpecBuilder()
+      .setContentType(ContentType.JSON)
+      .setAccept(ContentType.JSON)
+      .setConfig(newConfig.encoderConfig(encoderConfig.defaultContentCharset(StandardCharsets.UTF_8)))
+      .setPort(jmapServer.getPort.getValue)
+      .setBasePath(JMAP)
+      .build
+  }
+
+  after {
+    jmapServer.stop()
+  }
+
+  "RFC-8621 version, GET" should "not supported and return 404 status" in {
+    RestAssured
+      .`given`()
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .when()
+        .get
+      .then
+        .statusCode(HttpStatus.SC_NOT_FOUND)
+  }
+
+  "RFC-8621 version, POST, without body" should "return 200 status" in {
+    RestAssured
+      .`given`()
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .when()
+        .post
+      .then
+        .statusCode(HttpStatus.SC_OK)
+  }
+
+  "RFC-8621 version, POST, methods include supported" should "return OK status" in {
+    val response = RestAssured
+      .`given`()
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(REQUEST_OBJECT)
+      .when()
+        .post()
+      .then
+        .statusCode(HttpStatus.SC_OK)
+        .contentType(ContentType.JSON)
+      .extract()
+        .body()
+        .asString()
+
+    response shouldBe (RESPONSE_OBJECT)
+  }
+
+  "RFC-8621 version, POST, with methods" should "return OK status, ResponseObject depend on method" in {
+
+    val response = RestAssured
+      .`given`()
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(REQUEST_OBJECT_WITH_UNSUPPORTED_METHOD)
+      .when()
+        .post()
+      .then
+        .statusCode(HttpStatus.SC_OK)
+        .contentType(ContentType.JSON)
+      .extract()
+        .body()
+        .asString()
+
+    response shouldBe (RESPONSE_OBJECT_WITH_UNSUPPORTED_METHOD)
+  }
+
+  "Draft version, GET" should "return 404 status" in {
+    RestAssured
+      .`given`()
+        .header(ACCEPT.toString, ACCEPT_DRAFT_VERSION_HEADER)
+      .when()
+        .get
+      .then
+        .statusCode(HttpStatus.SC_NOT_FOUND)
+  }
+
+  "Draft version, POST, without body" should "return 400 status" in {
+    RestAssured
+      .`given`()
+        .header(ACCEPT.toString, ACCEPT_DRAFT_VERSION_HEADER)
+      .when()
+        .post
+      .then
+        .statusCode(HttpStatus.SC_NOT_FOUND)
+  }
+}


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