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:18 UTC

[james-project] 12/12: JAMES-3491 Experiment sttp for websocket client

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 e5cdea90d6f709f9cd01d653967944cec42f27fe
Author: Raphael Ouazana <ra...@linagora.com>
AuthorDate: Thu Jan 28 17:26:19 2021 +0100

    JAMES-3491 Experiment sttp for websocket client
---
 .../jmap-rfc-8621-integration-tests-common/pom.xml |  10 +-
 .../jmap/rfc8621/contract/WebSocketContract.scala  | 540 ++++++++++++---------
 .../james/jmap/json/ResponseSerializer.scala       |  11 +-
 3 files changed, 314 insertions(+), 247 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 b84b5f6..0fabc14 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
@@ -47,6 +47,11 @@
             <artifactId>testing-base</artifactId>
         </dependency>
         <dependency>
+            <groupId>com.softwaremill.sttp.client3</groupId>
+            <artifactId>okhttp-backend_${scala.base}</artifactId>
+            <version>3.0.0</version>
+        </dependency>
+        <dependency>
             <groupId>com.typesafe.play</groupId>
             <artifactId>play-json_${scala.base}</artifactId>
         </dependency>
@@ -62,11 +67,6 @@
             <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 7bf45a3..0f708c7d 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,33 +18,28 @@
  ****************************************************************/
 package org.apache.james.jmap.rfc8621.contract
 
-import java.net.URI
-import java.util
-import java.util.concurrent.TimeUnit
+import java.net.{ProtocolException, URI}
 
 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.WebSocketContract.{LOGGER, await}
-import org.apache.james.jmap.rfc8621.contract.tags.CategoryTags
 import org.apache.james.utils.DataProbeImpl
-import org.assertj.core.api.Assertions.assertThat
-import org.awaitility.Awaitility
-import org.java_websocket.client.WebSocketClient
-import org.java_websocket.handshake.ServerHandshake
-import org.junit.jupiter.api.{BeforeEach, Tag, Test}
-import org.slf4j.{Logger, LoggerFactory}
-
-object WebSocketContract {
-  val LOGGER: Logger = LoggerFactory.getLogger(classOf[WebSocketContract])
-
-  val await = Awaitility.await
-    .atMost(1, TimeUnit.SECONDS)
-    .pollInterval(100, TimeUnit.MILLISECONDS)
-}
+import org.assertj.core.api.Assertions.assertThatThrownBy
+import org.junit.jupiter.api.{BeforeEach, Test}
+import sttp.capabilities.WebSockets
+import sttp.client3.monad.IdMonad
+import sttp.client3.okhttp.OkHttpSyncBackend
+import sttp.client3.{Identity, RequestT, SttpBackend, asWebSocket, basicRequest}
+import sttp.model.Uri
+import sttp.monad.MonadError
+import sttp.monad.syntax.MonadErrorOps
+import sttp.ws.WebSocketFrame
+import sttp.ws.WebSocketFrame.Text
 
 trait WebSocketContract {
+  private lazy val backend: SttpBackend[Identity, WebSockets] = OkHttpSyncBackend()
+  private lazy implicit val monadError: MonadError[Identity] = IdMonad
 
   @BeforeEach
   def setUp(server: GuiceJamesServer): Unit = {
@@ -54,264 +49,335 @@ trait WebSocketContract {
       .addUser(BOB.asString(), BOB_PASSWORD)
   }
 
-  class TestClient(uri: URI) extends WebSocketClient(uri) {
-    val receivedResponses: util.LinkedList[String] = new util.LinkedList[String]()
-    var closeString: Option[String] = None
-
-    override def onOpen(serverHandshake: ServerHandshake): Unit = {}
-
-    override def onMessage(s: String): Unit = receivedResponses.add(s)
-
-    override def onClose(i: Int, s: String, b: Boolean): Unit = closeString = Some(s)
-
-    override def onError(e: Exception): Unit = LOGGER.error("WebSocket error", e)
-  }
-
   @Test
-  @Tag(CategoryTags.BASIC_FEATURE)
   def apiRequestsShouldBeProcessed(server: GuiceJamesServer): Unit = {
-    val client: TestClient = authenticatedWebSocketClient(server)
-    client.connectBlocking()
-    client.send("""{
-                  |  "@type": "Request",
-                  |  "requestId": "req-36",
-                  |  "using": [ "urn:ietf:params:jmap:core"],
-                  |  "methodCalls": [
-                  |    [
-                  |      "Core/echo",
-                  |      {
-                  |        "arg1": "arg1data",
-                  |        "arg2": "arg2data"
-                  |      },
-                  |      "c1"
-                  |    ]
-                  |  ]
-                  |}""".stripMargin)
-
-    await.until(() => client.receivedResponses.size() == 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)
+    val response: Either[String, String] =
+      authenticatedRequest(server)
+        .response(asWebSocket[Identity, String] {
+          ws =>
+            ws.send(WebSocketFrame.text(
+              """{
+                |  "@type": "Request",
+                |  "requestId": "req-36",
+                |  "using": [ "urn:ietf:params:jmap:core"],
+                |  "methodCalls": [
+                |    [
+                |      "Core/echo",
+                |      {
+                |        "arg1": "arg1data",
+                |        "arg2": "arg2data"
+                |      },
+                |      "c1"
+                |    ]
+                |  ]
+                |}""".stripMargin))
+
+            ws.receive()
+              .map { case t: Text => t.payload }
+        })
+        .send(backend)
+        .body
+
+    assertThatJson(response.toOption.get)
+      .isEqualTo("""{
+                   |  "@type":"Response",
+                   |  "requestId":"req-36",
+                   |  "sessionState":"2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+                   |  "methodResponses":[
+                   |    ["Core/echo",
+                   |      {
+                   |        "arg1":"arg1data",
+                   |        "arg2":"arg2data"
+                   |      },"c1"]
+                   |  ]
+                   |}
+                   |""".stripMargin)
   }
 
   @Test
   def apiRequestsShouldBeProcessedWhenNoRequestId(server: GuiceJamesServer): Unit = {
-    val client: TestClient = authenticatedWebSocketClient(server)
-    client.connectBlocking()
-    client.send("""{
-                  |  "@type": "Request",
-                  |  "using": [ "urn:ietf:params:jmap:core"],
-                  |  "methodCalls": [
-                  |    [
-                  |      "Core/echo",
-                  |      {
-                  |        "arg1": "arg1data",
-                  |        "arg2": "arg2data"
-                  |      },
-                  |      "c1"
-                  |    ]
-                  |  ]
-                  |}""".stripMargin)
-
-    await.untilAsserted(() => 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)
+    val response: Either[String, String] =
+      authenticatedRequest(server)
+        .response(asWebSocket[Identity, String] {
+          ws =>
+            ws.send(WebSocketFrame.text(
+              """{
+                |  "@type": "Request",
+                |  "using": [ "urn:ietf:params:jmap:core"],
+                |  "methodCalls": [
+                |    [
+                |      "Core/echo",
+                |      {
+                |        "arg1": "arg1data",
+                |        "arg2": "arg2data"
+                |      },
+                |      "c1"
+                |    ]
+                |  ]
+                |}""".stripMargin))
+
+            ws.receive()
+              .map { case t: Text => t.payload }
+        })
+        .send(backend)
+        .body
+
+    assertThatJson(response.toOption.get)
+      .isEqualTo("""{
+                   |  "@type":"Response",
+                   |  "requestId":null,
+                   |  "sessionState":"2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+                   |  "methodResponses":[["Core/echo",{"arg1":"arg1data","arg2":"arg2data"},"c1"]]
+                   |}""".stripMargin)
   }
 
   @Test
   def nonJsonPayloadShouldTriggerError(server: GuiceJamesServer): Unit = {
-    val client: TestClient = authenticatedWebSocketClient(server)
-    client.connectBlocking()
-    client.send("The quick brown fox".stripMargin)
-
-    await.untilAsserted(() => 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)
+    val response: Either[String, String] =
+      authenticatedRequest(server)
+        .response(asWebSocket[Identity, String] {
+          ws =>
+            ws.send(WebSocketFrame.text("The quick brown fox"))
+
+            ws.receive()
+              .map { case t: Text => t.payload }
+        })
+        .send(backend)
+        .body
+
+    assertThatJson(response.toOption.get)
+      .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 handshakeShouldBeAuthenticated(server: GuiceJamesServer): Unit = {
-    val client: TestClient = unauthenticatedWebSocketClient(server)
-    client.connectBlocking()
-
-    assertThat(client.isClosed).isTrue
-    assertThat(client.closeString).isEqualTo(Some("Invalid status code received: 401 Status line: HTTP/1.1 401 Unauthorized"))
+    assertThatThrownBy(() =>
+      unauthenticatedRequest(server)
+        .response(asWebSocket[Identity, String] {
+          ws =>
+            ws.send(WebSocketFrame.text("The quick brown fox"))
+
+          ws.receive()
+            .map { case t: Text => t.toString }
+      })
+      .send(backend)
+      .body)
+      .hasRootCause(new ProtocolException("Expected HTTP 101 response but was '401 Unauthorized'"))
   }
 
   @Test
-  def noTypeFiledShouldTriggerError(server: GuiceJamesServer): Unit = {
-    val client: TestClient = authenticatedWebSocketClient(server)
-    client.connectBlocking()
-    client.send("""{
-                  |  "requestId": "req-36",
-                  |  "using": [ "urn:ietf:params:jmap:core"],
-                  |  "methodCalls": [
-                  |    [
-                  |      "Core/echo",
-                  |      {
-                  |        "arg1": "arg1data",
-                  |        "arg2": "arg2data"
-                  |      },
-                  |      "c1"
-                  |    ]
-                  |  ]
-                  |}""".stripMargin)
-
-    await.until(() => client.receivedResponses.size() == 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)
+  def noTypeFieldShouldTriggerError(server: GuiceJamesServer): Unit = {
+    val response: Either[String, String] =
+      authenticatedRequest(server)
+        .response(asWebSocket[Identity, String] {
+          ws =>
+            ws.send(WebSocketFrame.text(
+              """{
+                |  "requestId": "req-36",
+                |  "using": [ "urn:ietf:params:jmap:core"],
+                |  "methodCalls": [
+                |    [
+                |      "Core/echo",
+                |      {
+                |        "arg1": "arg1data",
+                |        "arg2": "arg2data"
+                |      },
+                |      "c1"
+                |    ]
+                |  ]
+                |}""".stripMargin))
+
+            ws.receive()
+              .map { case t: Text => t.payload }
+        })
+        .send(backend)
+        .body
+
+    assertThatJson(response.toOption.get)
+      .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 field on a webSocket inbound message),ArraySeq()))))",
+                   |  "type":"urn:ietf:params:jmap:error:notRequest",
+                   |  "requestId":null,
+                   |  "@type":"RequestError"
+                   |}""".stripMargin)
   }
 
   @Test
   def badTypeFieldShouldTriggerError(server: GuiceJamesServer): Unit = {
-    val client: TestClient = authenticatedWebSocketClient(server)
-    client.connectBlocking()
-    client.send("""{
-                  |  "@type": 42,
-                  |  "requestId": "req-36",
-                  |  "using": [ "urn:ietf:params:jmap:core"],
-                  |  "methodCalls": [
-                  |    [
-                  |      "Core/echo",
-                  |      {
-                  |        "arg1": "arg1data",
-                  |        "arg2": "arg2data"
-                  |      },
-                  |      "c1"
-                  |    ]
-                  |  ]
-                  |}""".stripMargin)
-
-    await.untilAsserted(() => 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(Invalid @type filed on a webSocket inbound message: expecting a JsString, got 42),ArraySeq()))))",
-        |  "type":"urn:ietf:params:jmap:error:notRequest",
-        |  "requestId":null,
-        |  "@type":"RequestError"
-        |}""".stripMargin)
+    val response: Either[String, String] =
+      authenticatedRequest(server)
+        .response(asWebSocket[Identity, String] {
+          ws =>
+            ws.send(WebSocketFrame.text(
+              """{
+                |  "@type": 42,
+                |  "requestId": "req-36",
+                |  "using": [ "urn:ietf:params:jmap:core"],
+                |  "methodCalls": [
+                |    [
+                |      "Core/echo",
+                |      {
+                |        "arg1": "arg1data",
+                |        "arg2": "arg2data"
+                |      },
+                |      "c1"
+                |    ]
+                |  ]
+                |}""".stripMargin))
+
+            ws.receive()
+              .map { case t: Text => t.payload }
+        })
+        .send(backend)
+        .body
+
+    assertThatJson(response.toOption.get)
+      .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(Invalid @type field on a webSocket inbound message: expecting a JsString, got 42),ArraySeq()))))",
+                   |  "type":"urn:ietf:params:jmap:error:notRequest",
+                   |  "requestId":null,
+                   |  "@type":"RequestError"
+                   |}""".stripMargin)
   }
 
   @Test
   def unknownTypeFieldShouldTriggerError(server: GuiceJamesServer): Unit = {
-    val client: TestClient = authenticatedWebSocketClient(server)
-    client.connectBlocking()
-    client.send(
-      """{
-        |  "@type": "unknown",
-        |  "requestId": "req-36",
-        |  "using": [ "urn:ietf:params:jmap:core"],
-        |  "methodCalls": [
-        |    [
-        |      "Core/echo",
-        |      {
-        |        "arg1": "arg1data",
-        |        "arg2": "arg2data"
-        |      },
-        |      "c1"
-        |    ]
-        |  ]
-        |}""".stripMargin)
-
-    await.untilAsserted(() => 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(Unknown @type filed on a webSocket inbound message: unknown),ArraySeq()))))",
-        |  "type":"urn:ietf:params:jmap:error:notRequest",
-        |  "requestId":null,
-        |  "@type":"RequestError"
-        |}""".stripMargin)
+    val response: Either[String, String] =
+      authenticatedRequest(server)
+        .response(asWebSocket[Identity, String] {
+          ws =>
+            ws.send(WebSocketFrame.text(
+              """{
+                |  "@type": "unknown",
+                |  "requestId": "req-36",
+                |  "using": [ "urn:ietf:params:jmap:core"],
+                |  "methodCalls": [
+                |    [
+                |      "Core/echo",
+                |      {
+                |        "arg1": "arg1data",
+                |        "arg2": "arg2data"
+                |      },
+                |      "c1"
+                |    ]
+                |  ]
+                |}""".stripMargin))
+
+            ws.receive()
+              .map { case t: Text => t.payload }
+        })
+        .send(backend)
+        .body
+
+    assertThatJson(response.toOption.get)
+      .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(Unknown @type field on a webSocket inbound message: unknown),ArraySeq()))))",
+                   |  "type":"urn:ietf:params:jmap:error:notRequest",
+                   |  "requestId":null,
+                   |  "@type":"RequestError"
+                   |}""".stripMargin)
   }
 
-
   @Test
   def clientSendingARespondTypeFieldShouldTriggerError(server: GuiceJamesServer): Unit = {
-    val client: TestClient = authenticatedWebSocketClient(server)
-    client.connectBlocking()
-    client.send(
-      """{
-        |  "@type": "Response",
-        |  "requestId": "req-36",
-        |  "using": [ "urn:ietf:params:jmap:core"],
-        |  "methodCalls": [
-        |    [
-        |      "Core/echo",
-        |      {
-        |        "arg1": "arg1data",
-        |        "arg2": "arg2data"
-        |      },
-        |      "c1"
-        |    ]
-        |  ]
-        |}""".stripMargin)
-
-    await.untilAsserted(() => 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(Unknown @type filed on a webSocket inbound message: Response),ArraySeq()))))",
-        |  "type":"urn:ietf:params:jmap:error:notRequest",
-        |  "requestId":null,
-        |  "@type":"RequestError"
-        |}""".stripMargin)
+    val response: Either[String, String] =
+      authenticatedRequest(server)
+        .response(asWebSocket[Identity, String] {
+          ws =>
+            ws.send(WebSocketFrame.text(
+              """{
+                |  "@type": "Response",
+                |  "requestId": "req-36",
+                |  "using": [ "urn:ietf:params:jmap:core"],
+                |  "methodCalls": [
+                |    [
+                |      "Core/echo",
+                |      {
+                |        "arg1": "arg1data",
+                |        "arg2": "arg2data"
+                |      },
+                |      "c1"
+                |    ]
+                |  ]
+                |}""".stripMargin))
+
+            ws.receive()
+              .map { case t: Text => t.payload }
+        })
+        .send(backend)
+        .body
+
+    assertThatJson(response.toOption.get)
+      .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(Unknown @type field on a webSocket inbound message: Response),ArraySeq()))))",
+                   |  "type":"urn:ietf:params:jmap:error:notRequest",
+                   |  "requestId":null,
+                   |  "@type":"RequestError"
+                   |}""".stripMargin)
   }
 
   @Test
   def requestLevelErrorShouldReturnAPIError(server: GuiceJamesServer): Unit = {
-    val client: TestClient = authenticatedWebSocketClient(server)
-    client.connectBlocking()
-    client.send(s"""{
-                   |  "@type": "Request",
-                   |  "using": [
-                   |    "urn:ietf:params:jmap:core",
-                   |    "urn:ietf:params:jmap:mail"],
-                   |  "methodCalls": [[
-                   |      "Mailbox/get",
-                   |      {
-                   |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-                   |        "properties": ["invalidProperty"]
-                   |      },
-                   |      "c1"]]
+    val response: Either[String, String] =
+      authenticatedRequest(server)
+        .response(asWebSocket[Identity, String] {
+          ws =>
+            ws.send(WebSocketFrame.text(
+              """{
+                |  "@type": "Request",
+                |  "using": [
+                |    "urn:ietf:params:jmap:core",
+                |    "urn:ietf:params:jmap:mail"],
+                |  "methodCalls": [[
+                |      "Mailbox/get",
+                |      {
+                |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+                |        "properties": ["invalidProperty"]
+                |      },
+                |      "c1"]]
+                |}""".stripMargin))
+
+            ws.receive()
+              .map { case t: Text => t.payload }
+        })
+        .send(backend)
+        .body
+
+    assertThatJson(response.toOption.get)
+      .isEqualTo("""{
+                   |  "@type": "Response",
+                   |  "requestId": null,
+                   |  "sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+                   |  "methodResponses": [["error",{"type":"invalidArguments","description":"The following properties [invalidProperty] do not exist."},"c1"]]
                    |}""".stripMargin)
-
-    await.untilAsserted(() => assertThat(client.receivedResponses).hasSize(1))
-    assertThatJson(client.receivedResponses.get(0)).isEqualTo("""{
-        |  "@type": "Response",
-        |  "requestId": null,
-        |  "sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
-        |  "methodResponses": [["error",{"type":"invalidArguments","description":"The following properties [invalidProperty] do not exist."},"c1"]]
-        |}""".stripMargin)
   }
 
-  private def unauthenticatedWebSocketClient(server: GuiceJamesServer): TestClient = {
+  private def authenticatedRequest(server: GuiceJamesServer): RequestT[Identity, Either[String, String], Any] = {
     val port = server.getProbe(classOf[JmapGuiceProbe])
       .getJmapPort
       .getValue
-    val client = new TestClient(new URI(s"ws://127.0.0.1:$port/jmap/ws"))
-    client.addHeader("Accept", ACCEPT_RFC8621_VERSION_HEADER)
-    client
+
+    basicRequest.get(Uri.apply(new URI(s"ws://127.0.0.1:$port/jmap/ws")))
+      .header("Authorization", "Basic Ym9iQGRvbWFpbi50bGQ6Ym9icGFzc3dvcmQ=")
+      .header("Accept", ACCEPT_RFC8621_VERSION_HEADER)
   }
 
-  private def authenticatedWebSocketClient(server: GuiceJamesServer): TestClient = {
-    val client = unauthenticatedWebSocketClient(server)
-    client.addHeader("Authorization", "Basic Ym9iQGRvbWFpbi50bGQ6Ym9icGFzc3dvcmQ=")
-    client
+  private def unauthenticatedRequest(server: GuiceJamesServer): RequestT[Identity, Either[String, String], Any] = {
+    val port = server.getProbe(classOf[JmapGuiceProbe])
+      .getJmapPort
+      .getValue
+
+    basicRequest.get(Uri.apply(new URI(s"ws://127.0.0.1:$port/jmap/ws")))
+      .header("Accept", ACCEPT_RFC8621_VERSION_HEADER)
   }
 }
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/ResponseSerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/ResponseSerializer.scala
index ebf7eb4..71e04f7 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/ResponseSerializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/ResponseSerializer.scala
@@ -19,6 +19,9 @@
 
 package org.apache.james.jmap.json
 
+import java.io.InputStream
+import java.net.URL
+
 import eu.timepit.refined.refineV
 import io.netty.handler.codec.http.HttpResponseStatus
 import org.apache.james.core.Username
@@ -31,8 +34,6 @@ import org.apache.james.jmap.core.{Account, Invocation, Session, _}
 import play.api.libs.functional.syntax._
 import play.api.libs.json._
 
-import java.io.InputStream
-import java.net.URL
 import scala.collection.{Seq => LegacySeq}
 import scala.language.implicitConversions
 import scala.util.Try
@@ -188,9 +189,9 @@ object ResponseSerializer {
     case json: JsObject =>
       json.value.get("@type") match {
         case Some(JsString("Request")) => webSocketRequestReads.reads(json)
-        case Some(JsString(unknownType)) => JsError(s"Unknown @type filed on a webSocket inbound message: $unknownType")
-        case Some(invalidType) => JsError(s"Invalid @type filed on a webSocket inbound message: expecting a JsString, got $invalidType")
-        case None => JsError(s"Missing @type filed on a webSocket inbound message")
+        case Some(JsString(unknownType)) => JsError(s"Unknown @type field on a webSocket inbound message: $unknownType")
+        case Some(invalidType) => JsError(s"Invalid @type field on a webSocket inbound message: expecting a JsString, got $invalidType")
+        case None => JsError(s"Missing @type field on a webSocket inbound message")
       }
     case _ => JsError("Expecting a JsObject to represent a webSocket inbound message")
   }


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