You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2021/02/03 02:51:14 UTC

[james-project] 08/12: JAMES-3491 WIP write tests for RFC-8887 JMAP over websocket support

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 34f1a6888b26bd3d86edcbc46b06d5c8d28786e8
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Jan 28 17:25:10 2021 +0700

    JAMES-3491 WIP write tests for RFC-8887 JMAP over websocket support
    
    Exercise only the transport layer, no PUSH support yet.
---
 .../jmap-rfc-8621-integration-tests-common/pom.xml |   9 +
 .../jmap/rfc8621/contract/WebSocketContract.scala  | 225 +++++++++++++++++++--
 2 files changed, 215 insertions(+), 19 deletions(-)

diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/pom.xml b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/pom.xml
index 539a520..b84b5f6 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/pom.xml
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/pom.xml
@@ -40,6 +40,10 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-jmap-draft</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
             <artifactId>testing-base</artifactId>
         </dependency>
         <dependency>
@@ -58,6 +62,11 @@
             <groupId>org.awaitility</groupId>
             <artifactId>awaitility</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.java-websocket</groupId>
+            <artifactId>Java-WebSocket</artifactId>
+            <version>1.5.1</version>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/WebSocketContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/WebSocketContract.scala
index ae91a42..73ea439 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/WebSocketContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/WebSocketContract.scala
@@ -18,10 +18,18 @@
  ****************************************************************/
 package org.apache.james.jmap.rfc8621.contract
 
+import java.net.URI
+import java.util
+
+import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
 import org.apache.james.GuiceJamesServer
+import org.apache.james.jmap.draft.JmapGuiceProbe
 import org.apache.james.jmap.rfc8621.contract.Fixture._
 import org.apache.james.jmap.rfc8621.contract.tags.CategoryTags
 import org.apache.james.utils.DataProbeImpl
+import org.assertj.core.api.Assertions.assertThat
+import org.java_websocket.client.WebSocketClient
+import org.java_websocket.handshake.ServerHandshake
 import org.junit.jupiter.api.{BeforeEach, Tag, Test}
 
 trait WebSocketContract {
@@ -33,51 +41,230 @@ trait WebSocketContract {
       .addUser(BOB.asString(), BOB_PASSWORD)
   }
 
+  class ExampleClient(uri: URI) extends WebSocketClient(uri) {
+    val receivedResponses: util.LinkedList[String] = new util.LinkedList[String]()
+    var closeCode: Option[Integer] = None
+    var closeString: Option[String] = None
+
+    override def onOpen(serverHandshake: ServerHandshake): Unit = {
+      println(s"handshake ${serverHandshake.getHttpStatus}")
+    }
+
+    override def onMessage(s: String): Unit = {
+      println(s"Received: $s")
+      receivedResponses.add(s)
+    }
+
+    override def onClose(i: Int, s: String, b: Boolean): Unit = {
+      closeCode = Some(i)
+      closeString = Some(s)
+      println(s"Closing connection $i $s $b")
+    }
+
+    override def onError(e: Exception): Unit = {
+      println("Error: " + e.getMessage)
+    }
+  }
+
   @Test
   @Tag(CategoryTags.BASIC_FEATURE)
-  def apiRequestsShouldBeProcessed(): Unit = {
-    /*
-    * TODO test an echo response - request (success)
-    * */
+  def apiRequestsShouldBeProcessed(server: GuiceJamesServer): Unit = {
+    println("started")
+    val port = server.getProbe(classOf[JmapGuiceProbe])
+      .getJmapPort
+      .getValue
+    val client = new ExampleClient(new URI(s"ws://127.0.0.1:$port/jmap/ws"))
+    client.addHeader("Authorization", "Basic Ym9iQGRvbWFpbi50bGQ6Ym9icGFzc3dvcmQ=")
+    client.addHeader("Accept", ACCEPT_RFC8621_VERSION_HEADER)
+
+    client.connectBlocking()
+
+    Thread.sleep(500)
+
+    client.send("""{
+                  |  "@type": "Request",
+                  |  "requestId": "req-36",
+                  |  "using": [ "urn:ietf:params:jmap:core"],
+                  |  "methodCalls": [
+                  |    [
+                  |      "Core/echo",
+                  |      {
+                  |        "arg1": "arg1data",
+                  |        "arg2": "arg2data"
+                  |      },
+                  |      "c1"
+                  |    ]
+                  |  ]
+                  |}""".stripMargin)
+
+    Thread.sleep(500)
+
+
+    assertThat(client.receivedResponses).hasSize(1)
+    assertThatJson(client.receivedResponses.get(0)).isEqualTo(
+      """
+        |{
+        |  "@type":"Response",
+        |  "requestId":"req-36",
+        |  "sessionState":"2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+        |  "methodResponses":[["Core/echo",{"arg1":"arg1data","arg2":"arg2data"},"c1"]]
+        |}
+        |""".stripMargin)
   }
 
   @Test
-  def nonJsonPayloadShouldTriggerError(): Unit = {
-    /*
-    * TODO send 'the quick brown fox' and get an error level error
-    * */
+  def apiRequestsShouldBeProcessedWhenNoRequestId(server: GuiceJamesServer): Unit = {
+    println("started")
+    val port = server.getProbe(classOf[JmapGuiceProbe])
+      .getJmapPort
+      .getValue
+    val client = new ExampleClient(new URI(s"ws://127.0.0.1:$port/jmap/ws"))
+    client.addHeader("Authorization", "Basic Ym9iQGRvbWFpbi50bGQ6Ym9icGFzc3dvcmQ=")
+    client.addHeader("Accept", ACCEPT_RFC8621_VERSION_HEADER)
+
+    client.connectBlocking()
+
+    Thread.sleep(500)
+
+    client.send("""{
+                  |  "@type": "Request",
+                  |  "using": [ "urn:ietf:params:jmap:core"],
+                  |  "methodCalls": [
+                  |    [
+                  |      "Core/echo",
+                  |      {
+                  |        "arg1": "arg1data",
+                  |        "arg2": "arg2data"
+                  |      },
+                  |      "c1"
+                  |    ]
+                  |  ]
+                  |}""".stripMargin)
+
+    Thread.sleep(500)
+
+
+    assertThat(client.receivedResponses).hasSize(1)
+    assertThatJson(client.receivedResponses.get(0)).isEqualTo(
+      """
+        |{
+        |  "@type":"Response",
+        |  "requestId":null,
+        |  "sessionState":"2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+        |  "methodResponses":[["Core/echo",{"arg1":"arg1data","arg2":"arg2data"},"c1"]]
+        |}
+        |""".stripMargin)
   }
 
   @Test
-  def handshakeShouldBeAuthenticated(): Unit = {
-    /*
-    * TODO set up no auth
-    * */
+  def nonJsonPayloadShouldTriggerError(server: GuiceJamesServer): Unit = {
+    println("started")
+    val port = server.getProbe(classOf[JmapGuiceProbe])
+      .getJmapPort
+      .getValue
+    val client = new ExampleClient(new URI(s"ws://127.0.0.1:$port/jmap/ws"))
+    client.addHeader("Authorization", "Basic Ym9iQGRvbWFpbi50bGQ6Ym9icGFzc3dvcmQ=")
+    client.addHeader("Accept", ACCEPT_RFC8621_VERSION_HEADER)
+
+    client.connectBlocking()
+
+    Thread.sleep(500)
+
+    client.send("The quick brown fox".stripMargin)
+
+    Thread.sleep(500)
+
+    assertThat(client.receivedResponses).hasSize(1)
+    assertThatJson(client.receivedResponses.get(0)).isEqualTo(
+      """
+        |{
+        |  "status":400,
+        |  "detail":"The request was successfully parsed as JSON but did not match the type signature of the Request object: List((,List(JsonValidationError(List(Unrecognized token 'The': was expecting ('true', 'false' or 'null')\n at [Source: (String)\"The quick brown fox\"; line: 1, column: 4]),ArraySeq()))))",
+        |  "type":"urn:ietf:params:jmap:error:notRequest",
+        |  "requestId":null,
+        |  "@type":"RequestError"
+        |}
+        |""".stripMargin)
   }
 
   @Test
-  def noTypeFiledShouldTriggerError(): Unit = {
-    /*
-    * TODO send something without @type and get an error level error
-    * */
+  def handshakeShouldBeAuthenticated(server: GuiceJamesServer): Unit = {
+    val port = server.getProbe(classOf[JmapGuiceProbe])
+      .getJmapPort
+      .getValue
+    val client = new ExampleClient(new URI(s"ws://127.0.0.1:$port/jmap/ws"))
+    client.addHeader("Accept", ACCEPT_RFC8621_VERSION_HEADER)
+
+    client.connectBlocking()
+
+    Thread.sleep(100)
+
+    assertThat(client.isClosed).isTrue
+    assertThat(client.closeString).isEqualTo(Some("Invalid status code received: 401 Status line: HTTP/1.1 401 Unauthorized"))
+  }
+
+  @Test
+  def noTypeFiledShouldTriggerError(server: GuiceJamesServer): Unit = {
+    println("started")
+    val port = server.getProbe(classOf[JmapGuiceProbe])
+      .getJmapPort
+      .getValue
+    val client = new ExampleClient(new URI(s"ws://127.0.0.1:$port/jmap/ws"))
+    client.addHeader("Authorization", "Basic Ym9iQGRvbWFpbi50bGQ6Ym9icGFzc3dvcmQ=")
+    client.addHeader("Accept", ACCEPT_RFC8621_VERSION_HEADER)
+
+    client.connectBlocking()
+
+    Thread.sleep(500)
+
+    client.send("""{
+                  |  "requestId": "req-36",
+                  |  "using": [ "urn:ietf:params:jmap:core"],
+                  |  "methodCalls": [
+                  |    [
+                  |      "Core/echo",
+                  |      {
+                  |        "arg1": "arg1data",
+                  |        "arg2": "arg2data"
+                  |      },
+                  |      "c1"
+                  |    ]
+                  |  ]
+                  |}""".stripMargin)
+
+    Thread.sleep(500)
+
+
+    assertThat(client.receivedResponses).hasSize(1)
+    assertThatJson(client.receivedResponses.get(0)).isEqualTo(
+      """
+        |{
+        |  "status":400,
+        |  "detail":"The request was successfully parsed as JSON but did not match the type signature of the Request object: List((,List(JsonValidationError(List(Missing @type filed on a webSocket inbound message),ArraySeq()))))",
+        |  "type":"urn:ietf:params:jmap:error:notRequest",
+        |  "requestId":null,
+        |  "@type":"RequestError"
+        |}
+        |""".stripMargin
+    )
   }
 
   @Test
-  def badTypeFieldShouldTriggerError(): Unit = {
+  def badTypeFieldShouldTriggerError(server: GuiceJamesServer): Unit = {
     /*
     * TODO send something with @type being a JsNumber and get an error level error
     * */
   }
 
   @Test
-  def unknownTypeFieldShouldTriggerError(): Unit = {
+  def unknownTypeFieldShouldTriggerError(server: GuiceJamesServer): Unit = {
     /*
     * TODO send something with @type being a JsString("unknown") and get an error level error
     * */
   }
 
   @Test
-  def requestLevelErrorShouldReturnAPIError(): Unit = {
+  def requestLevelErrorShouldReturnAPIError(server: GuiceJamesServer): Unit = {
     /*
     * TODO send a request triggering a method level error (eg Mailbox/get with an invalid JSON payload)
     * */


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