/****************************************************************
 * 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.rfc8621.contract

import com.google.common.collect.ImmutableSet
import com.google.inject.AbstractModule
import com.google.inject.multibindings.Multibinder
import com.google.crypto.tink.subtle.EllipticCurves.CurveType
import com.google.crypto.tink.subtle.{EllipticCurves, Random}
import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
import io.restassured.RestAssured.{`given`, requestSpecification}
import io.restassured.http.ContentType.JSON
import javax.inject.Inject
import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
import org.apache.http.HttpStatus.SC_OK
import org.apache.james.GuiceJamesServer
import org.apache.james.core.Username
import org.apache.james.jmap.api.model.{DeviceClientId, PushSubscription, PushSubscriptionCreationRequest, PushSubscriptionId, PushSubscriptionServerURL, TypeName}
import org.apache.james.jmap.api.pushsubscription.PushSubscriptionRepository
import org.apache.james.jmap.change.{EmailDeliveryTypeName, EmailTypeName, MailboxTypeName}
import org.apache.james.jmap.core.ResponseObject.SESSION_STATE
import org.apache.james.jmap.core.UTCDate
import org.apache.james.jmap.http.UserCredential
import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, BOB, BOB_PASSWORD, DOMAIN, authScheme, baseRequestSpecBuilder}
import org.apache.james.jmap.rfc8621.contract.PushSubscriptionSetMethodContract.TIME_FORMATTER
import org.apache.james.utils.{DataProbeImpl, GuiceProbe}
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.SoftAssertions
import org.junit.jupiter.api.{BeforeEach, Test}
import reactor.core.scala.publisher.SMono
import org.mockserver.integration.ClientAndServer
import org.mockserver.model.HttpRequest.request
import org.mockserver.model.HttpResponse.response
import org.mockserver.model.JsonBody.json
import org.mockserver.model.Not.not
import org.mockserver.model.NottableString.string
import org.mockserver.model.{HttpRequest, HttpResponse}
import org.mockserver.verify.VerificationTimes

import java.net.URL
import java.security.KeyPair
import java.security.interfaces.ECPublicKey
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.util.Base64
import java.util.UUID

import scala.jdk.CollectionConverters._

object PushSubscriptionSetMethodContract {
  val TIME_FORMATTER: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssX")
}
class PushSubscriptionProbe @Inject()(pushSubscriptionRepository: PushSubscriptionRepository) extends GuiceProbe {
  def createPushSubscription(username: Username, url: PushSubscriptionServerURL, deviceId: DeviceClientId, types: Seq[TypeName]): PushSubscription =
    SMono(pushSubscriptionRepository.save(username, PushSubscriptionCreationRequest(
      deviceClientId = deviceId,
      url = url,
      types = types)))
      .block()

  def retrievePushSubscription(username: Username, id: PushSubscriptionId): PushSubscription =
    SMono(pushSubscriptionRepository.get(username, ImmutableSet.of(id))).block()

  def validatePushSubscription(username: Username, id: PushSubscriptionId): Void =
    SMono(pushSubscriptionRepository.validateVerificationCode(username, id)).block()
}

class PushSubscriptionProbeModule extends AbstractModule {
  override def configure(): Unit = {
    Multibinder.newSetBinder(binder(), classOf[GuiceProbe])
      .addBinding()
      .to(classOf[PushSubscriptionProbe])
  }
}

trait PushSubscriptionSetMethodContract {
  @BeforeEach
  def setUp(server: GuiceJamesServer, pushServer: ClientAndServer): Unit = {
    server.getProbe(classOf[DataProbeImpl])
      .fluent()
      .addDomain(DOMAIN.asString())
      .addUser(BOB.asString(), BOB_PASSWORD)

    requestSpecification = baseRequestSpecBuilder(server)
      .setAuth(authScheme(UserCredential(BOB, BOB_PASSWORD)))
      .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
      .build()

    pushServer
      .when(request
        .withPath("/subscribe")
        .withMethod("POST")
        .withHeader(string("Content-type"), string("application/json charset=utf-8")))
      .respond(response
        .withStatusCode(201))
  }

  private def getPushServerUrl(pushServer: ClientAndServer) : String =
    s"http://127.0.0.1:${pushServer.getLocalPort}/subscribe"

  @Test
  def setMethodShouldNotRequireAccountId(): Unit = {
    val request: String =
      """{
        |    "using": ["urn:ietf:params:jmap:core"],
        |    "methodCalls": [
        |      [
        |        "PushSubscription/set",
        |        {
        |            "create": {
        |                "4f29": {
        |                  "deviceClientId": "a889-ffea-910",
        |                  "url": "https://example.com/push/?device=X8980fc&client=12c6d086",
        |                  "types": ["Mailbox"]
        |                }
        |              }
        |        },
        |        "c1"
        |      ]
        |    ]
        |  }""".stripMargin

    `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
  }

  @Test
  def setMethodShouldFailWhenMissingCapability(): Unit = {
    val request: String =
      """{
        |    "using": [],
        |    "methodCalls": [
        |      [
        |        "PushSubscription/set",
        |        {
        |            "create": {
        |                "4f29": {
        |                  "deviceClientId": "a889-ffea-910",
        |                  "url": "https://example.com/push/?device=X8980fc&client=12c6d086",
        |                  "types": ["Mailbox"]
        |                }
        |              }
        |        },
        |        "c1"
        |      ]
        |    ]
        |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "error",
           |            {
           |                "type": "unknownMethod",
           |                "description": "Missing capability(ies): urn:ietf:params:jmap:core"
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def updateShouldModifyTypes(server: GuiceJamesServer): Unit = {
    val probe = server.getProbe(classOf[PushSubscriptionProbe])
    val pushSubscription = probe
      .createPushSubscription(username = BOB,
        url = PushSubscriptionServerURL(new URL("https://example.com/push/?device=X8980fc&client=12c6d086")),
        deviceId = DeviceClientId("12c6d086"),
        types = Seq(MailboxTypeName))

    val request: String =
      s"""{
         |    "using": ["urn:ietf:params:jmap:core"],
         |    "methodCalls": [
         |      [
         |        "PushSubscription/set",
         |        {
         |            "update": {
         |                "${pushSubscription.id.serialise}": {
         |                  "types": ["Mailbox", "Email"]
         |                }
         |              }
         |        },
         |        "c1"
         |      ]
         |    ]
         |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "updated": {
           |                    "${pushSubscription.id.serialise}": {}
           |                }
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)

    assertThat(probe.retrievePushSubscription(BOB, pushSubscription.id).types.asJava)
      .containsExactlyInAnyOrder(MailboxTypeName, EmailTypeName)
  }

  @Test
  def updateShouldRejectUnknownTypes(server: GuiceJamesServer): Unit = {
    val probe = server.getProbe(classOf[PushSubscriptionProbe])
    val pushSubscription = probe
      .createPushSubscription(username = BOB,
        url = PushSubscriptionServerURL(new URL("https://example.com/push/?device=X8980fc&client=12c6d086")),
        deviceId = DeviceClientId("12c6d086"),
        types = Seq(MailboxTypeName))

    val request: String =
      s"""{
         |    "using": ["urn:ietf:params:jmap:core"],
         |    "methodCalls": [
         |      [
         |        "PushSubscription/set",
         |        {
         |            "update": {
         |                "${pushSubscription.id.serialise}": {
         |                  "types": ["Mailbox", "Unknown"]
         |                }
         |              }
         |        },
         |        "c1"
         |      ]
         |    ]
         |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "notUpdated":{
           |                    "${pushSubscription.id.serialise}":{
           |                        "type":"invalidArguments",
           |                        "description":"Unknown typeName Unknown",
           |                        "properties":["types"]
           |                    }
           |                }
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def updateShouldRejectBadTypes(server: GuiceJamesServer): Unit = {
    val probe = server.getProbe(classOf[PushSubscriptionProbe])
    val pushSubscription = probe
      .createPushSubscription(username = BOB,
        url = PushSubscriptionServerURL(new URL("https://example.com/push/?device=X8980fc&client=12c6d086")),
        deviceId = DeviceClientId("12c6d086"),
        types = Seq(MailboxTypeName))

    val request: String =
      s"""{
         |    "using": ["urn:ietf:params:jmap:core"],
         |    "methodCalls": [
         |      [
         |        "PushSubscription/set",
         |        {
         |            "update": {
         |                "${pushSubscription.id.serialise}": {
         |                  "types": 36
         |                }
         |              }
         |        },
         |        "c1"
         |      ]
         |    ]
         |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "notUpdated":{
           |                    "${pushSubscription.id.serialise}":{
           |                        "type":"invalidArguments",
           |                        "description":"Expecting an array of JSON strings as an argument",
           |                        "properties":["types"]
           |                    }
           |                }
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def updateShouldRejectBadType(server: GuiceJamesServer): Unit = {
    val probe = server.getProbe(classOf[PushSubscriptionProbe])
    val pushSubscription = probe
      .createPushSubscription(username = BOB,
        url = PushSubscriptionServerURL(new URL("https://example.com/push/?device=X8980fc&client=12c6d086")),
        deviceId = DeviceClientId("12c6d086"),
        types = Seq(MailboxTypeName))

    val request: String =
      s"""{
         |    "using": ["urn:ietf:params:jmap:core"],
         |    "methodCalls": [
         |      [
         |        "PushSubscription/set",
         |        {
         |            "update": {
         |                "${pushSubscription.id.serialise}": {
         |                  "types": ["Email", 36]
         |                }
         |              }
         |        },
         |        "c1"
         |      ]
         |    ]
         |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "notUpdated":{
           |                    "${pushSubscription.id.serialise}":{
           |                        "type":"invalidArguments",
           |                        "description":"Expecting an array of JSON strings as an argument",
           |                        "properties":["types"]
           |                    }
           |                }
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def setMethodShouldNotCreatedWhenMissingTypesPropertyInCreationRequest(): Unit = {
    val request: String =
      """{
        |    "using": ["urn:ietf:params:jmap:core"],
        |    "methodCalls": [
        |      [
        |        "PushSubscription/set",
        |        {
        |            "create": {
        |                "4f29": {
        |                  "deviceClientId": "a889-ffea-910",
        |                  "url": "https://example.com/push/?device=X8980fc&client=12c6d086"
        |                }
        |              }
        |        },
        |        "c1"
        |      ]
        |    ]
        |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "notCreated": {
           |                    "4f29": {
           |                        "type": "invalidArguments",
           |                        "description": "Missing '/types' property in PushSubscription object"
           |                    }
           |                }
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def setMethodShouldNotCreatedWhenTypesPropertyIsEmpty(): Unit = {
    val request: String =
      """{
        |    "using": ["urn:ietf:params:jmap:core"],
        |    "methodCalls": [
        |      [
        |        "PushSubscription/set",
        |        {
        |            "create": {
        |                "4f29": {
        |                  "deviceClientId": "a889-ffea-910",
        |                  "url": "https://example.com/push/?device=X8980fc&client=12c6d086",
        |                  "types": []
        |                }
        |              }
        |        },
        |        "c1"
        |      ]
        |    ]
        |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "notCreated": {
           |                    "4f29": {
           |                        "type": "invalidArguments",
           |                        "description": "types must not be empty"
           |                    }
           |                }
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def getShouldReturnEmptyWhenNone(): Unit = {
    val request: String =
      """{
        |    "using": ["urn:ietf:params:jmap:core"],
        |    "methodCalls": [ [ "PushSubscription/get", { }, "c1" ] ]
        |}""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/get",
           |            {
           |                "list": []
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def getShouldReturnAllRecords(server: GuiceJamesServer): Unit = {
    val probe = server.getProbe(classOf[PushSubscriptionProbe])
    val pushSubscription1 = probe
      .createPushSubscription(username = BOB,
        url = PushSubscriptionServerURL(new URL("https://example.com/push/?device=X8980fc&client=12c6d086")),
        deviceId = DeviceClientId("12c6d086"),
        types = Seq(MailboxTypeName))
    val pushSubscription2 = probe
      .createPushSubscription(username = BOB,
        url = PushSubscriptionServerURL(new URL("https://example.com/push/?device=X8980fc&client=12c6d086")),
        deviceId = DeviceClientId("12c6d087"),
        types = Seq(EmailTypeName))

    val request: String =
      """{
        |    "using": ["urn:ietf:params:jmap:core"],
        |    "methodCalls": [ [ "PushSubscription/get", { }, "c1" ] ]
        |}""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/get",
           |            {
           |                "list": [
           |                    {
           |                        "id": "${pushSubscription1.id.serialise}",
           |                        "deviceClientId": "12c6d086",
           |                        "expires": "${UTCDate(pushSubscription1.expires.value).asUTC.format(TIME_FORMATTER)}",
           |                        "types": ["Mailbox"]
           |                    },
           |                    {
           |                        "id": "${pushSubscription2.id.serialise}",
           |                        "deviceClientId": "12c6d087",
           |                        "expires": "${UTCDate(pushSubscription2.expires.value).asUTC.format(TIME_FORMATTER)}",
           |                        "types": ["Email"]
           |                    }
           |                ]
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def getShouldReturnValidatedVerificationCode(server: GuiceJamesServer): Unit = {
    val probe = server.getProbe(classOf[PushSubscriptionProbe])
    val pushSubscription1 = probe
      .createPushSubscription(username = BOB,
        url = PushSubscriptionServerURL(new URL("https://example.com/push/?device=X8980fc&client=12c6d086")),
        deviceId = DeviceClientId("12c6d086"),
        types = Seq(MailboxTypeName))
    probe.validatePushSubscription(BOB, pushSubscription1.id)

    val request: String =
      """{
        |    "using": ["urn:ietf:params:jmap:core"],
        |    "methodCalls": [ [ "PushSubscription/get", { }, "c1" ] ]
        |}""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/get",
           |            {
           |                "list": [
           |                    {
           |                        "id": "${pushSubscription1.id.serialise}",
           |                        "deviceClientId": "12c6d086",
           |                        "expires": "${UTCDate(pushSubscription1.expires.value).asUTC.format(TIME_FORMATTER)}",
           |                        "verificationCode": "${pushSubscription1.verificationCode.value}",
           |                        "types": ["Mailbox"]
           |                    }
           |                ]
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def getByIdShouldReturnRecords(server: GuiceJamesServer): Unit = {
    val probe = server.getProbe(classOf[PushSubscriptionProbe])
    val pushSubscription1 = probe
      .createPushSubscription(username = BOB,
        url = PushSubscriptionServerURL(new URL("https://example.com/push/?device=X8980fc&client=12c6d086")),
        deviceId = DeviceClientId("12c6d086"),
        types = Seq(MailboxTypeName))
    val pushSubscription2 = probe
      .createPushSubscription(username = BOB,
        url = PushSubscriptionServerURL(new URL("https://example.com/push/?device=X8980fc&client=12c6d086")),
        deviceId = DeviceClientId("12c6d087"),
        types = Seq(EmailTypeName))
    val pushSubscription3 = probe
      .createPushSubscription(username = BOB,
        url = PushSubscriptionServerURL(new URL("https://example.com/push/?device=X8980fc&client=12c6d086")),
        deviceId = DeviceClientId("12c6d088"),
        types = Seq(EmailTypeName))

    val request: String =
      s"""{
         |    "using": ["urn:ietf:params:jmap:core"],
         |    "methodCalls": [ [ "PushSubscription/get", {
         |       "ids": ["${pushSubscription1.id.serialise}", "${pushSubscription2.id.serialise}"]
         |     }, "c1" ] ]
         |}""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/get",
           |            {
           |                "list": [
           |                    {
           |                        "id": "${pushSubscription1.id.serialise}",
           |                        "deviceClientId": "12c6d086",
           |                        "expires": "${UTCDate(pushSubscription1.expires.value).asUTC.format(TIME_FORMATTER)}",
           |                        "types": ["Mailbox"]
           |                    },
           |                    {
           |                        "id": "${pushSubscription2.id.serialise}",
           |                        "deviceClientId": "12c6d087",
           |                        "expires": "${UTCDate(pushSubscription2.expires.value).asUTC.format(TIME_FORMATTER)}",
           |                        "types": ["Email"]
           |                    }
           |                ]
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def getByIdShouldReturnEmptyWhenEmpty(server: GuiceJamesServer): Unit = {
    val probe = server.getProbe(classOf[PushSubscriptionProbe])
    val pushSubscription1 = probe
      .createPushSubscription(username = BOB,
        url = PushSubscriptionServerURL(new URL("https://example.com/push/?device=X8980fc&client=12c6d086")),
        deviceId = DeviceClientId("12c6d086"),
        types = Seq(MailboxTypeName))

    val request: String =
      s"""{
         |    "using": ["urn:ietf:params:jmap:core"],
         |    "methodCalls": [ [ "PushSubscription/get", {
         |       "ids": []
         |     }, "c1" ] ]
         |}""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/get",
           |            {
           |                "list": []
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def getByIdShouldReturnNotFound(): Unit = {
    val request: String =
      s"""{
         |    "using": ["urn:ietf:params:jmap:core"],
         |    "methodCalls": [ [ "PushSubscription/get", {
         |       "ids": ["notFound"]
         |     }, "c1" ] ]
         |}""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/get",
           |            {
           |                "notFound": ["notFound"],
           |                "list": []
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def getShouldFilterProperties(server: GuiceJamesServer): Unit = {
    val probe = server.getProbe(classOf[PushSubscriptionProbe])
    val pushSubscription1 = probe
      .createPushSubscription(username = BOB,
        url = PushSubscriptionServerURL(new URL("https://example.com/push/?device=X8980fc&client=12c6d086")),
        deviceId = DeviceClientId("12c6d086"),
        types = Seq(MailboxTypeName))
    val pushSubscription2 = probe
      .createPushSubscription(username = BOB,
        url = PushSubscriptionServerURL(new URL("https://example.com/push/?device=X8980fc&client=12c6d086")),
        deviceId = DeviceClientId("12c6d087"),
        types = Seq(EmailTypeName))

    val request: String =
      """{
        |    "using": ["urn:ietf:params:jmap:core"],
        |    "methodCalls": [ [ "PushSubscription/get", { "properties": ["types"] }, "c1" ] ]
        |}""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/get",
           |            {
           |                "list": [
           |                    {
           |                        "id": "${pushSubscription1.id.serialise}",
           |                        "types": ["Mailbox"]
           |                    },
           |                    {
           |                        "id": "${pushSubscription2.id.serialise}",
           |                        "types": ["Email"]
           |                    }
           |                ]
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }


  @Test
  def setMethodShouldNotCreatedWhenInvalidURLProperty(): Unit = {
    val request: String =
      """{
        |    "using": ["urn:ietf:params:jmap:core"],
        |    "methodCalls": [
        |      [
        |        "PushSubscription/set",
        |        {
        |            "create": {
        |                "4f29": {
        |                  "deviceClientId": "a889-ffea-910",
        |                  "url": "invalid",
        |                  "types": ["Mailbox"]
        |                }
        |              }
        |        },
        |        "c1"
        |      ]
        |    ]
        |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "notCreated": {
           |                    "4f29": {
           |                        "type": "invalidArguments",
           |                        "description": "'/url' property in PushSubscription object is not valid"
           |                    }
           |                }
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def setMethodShouldNotCreatedWhenCreationRequestHasVerificationCodeProperty(): Unit = {
    val request: String =
      """{
        |    "using": ["urn:ietf:params:jmap:core"],
        |    "methodCalls": [
        |      [
        |        "PushSubscription/set",
        |        {
        |            "create": {
        |                "4f29": {
        |                  "deviceClientId": "a889-ffea-910",
        |                  "url": "https://example.com/push/?device=X8980fc&client=12c6d086",
        |                  "types": ["Mailbox"],
        |                  "verificationCode": "abc"
        |                }
        |              }
        |        },
        |        "c1"
        |      ]
        |    ]
        |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "notCreated": {
           |                    "4f29": {
           |                        "type": "invalidArguments",
           |                        "description": "Some server-set properties were specified",
           |                        "properties": [
           |                            "verificationCode"
           |                        ]
           |                    }
           |                }
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def setMethodShouldNotCreatedWhenCreationRequestHasIdProperty(): Unit = {
    val request: String =
      """{
        |    "using": ["urn:ietf:params:jmap:core"],
        |    "methodCalls": [
        |      [
        |        "PushSubscription/set",
        |        {
        |            "create": {
        |                "4f29": {
        |                  "deviceClientId": "a889-ffea-910",
        |                  "url": "https://example.com/push/?device=X8980fc&client=12c6d086",
        |                  "types": ["Mailbox"],
        |                  "id": "abc"
        |                }
        |              }
        |        },
        |        "c1"
        |      ]
        |    ]
        |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "notCreated": {
           |                    "4f29": {
           |                        "type": "invalidArguments",
           |                        "description": "Some server-set properties were specified",
           |                        "properties": [
           |                            "id"
           |                        ]
           |                    }
           |                }
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def setMethodShouldNotCreatedWhenInValidExpiresProperty(): Unit = {
    val invalidExpire: String = UTCDate(ZonedDateTime.now().minusDays(1)).asUTC.format(TIME_FORMATTER)
    val request: String =
      s"""{
         |    "using": ["urn:ietf:params:jmap:core"],
         |    "methodCalls": [
         |      [
         |        "PushSubscription/set",
         |        {
         |            "create": {
         |                "4f29": {
         |                  "deviceClientId": "a889-ffea-910",
         |                  "url": "https://example.com/push/?device=X8980fc&client=12c6d086",
         |                  "expires": "$invalidExpire",
         |                  "types": ["Mailbox"]
         |                }
         |              }
         |        },
         |        "c1"
         |      ]
         |    ]
         |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "notCreated": {
           |                    "4f29": {
           |                        "type": "invalidArguments",
           |                        "description": "`$invalidExpire` expires must be greater than now",
           |                        "properties": ["expires"]
           |                    }
           |                }
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def setMethodShouldNotCreatedWhenInValidTypesProperty(): Unit = {
    val request: String =
      s"""{
         |    "using": ["urn:ietf:params:jmap:core"],
         |    "methodCalls": [
         |      [
         |        "PushSubscription/set",
         |        {
         |            "create": {
         |                "4f29": {
         |                  "deviceClientId": "a889-ffea-910",
         |                  "url": "https://example.com/push/?device=X8980fc&client=12c6d086",
         |                  "types": ["invalid"]
         |                }
         |              }
         |        },
         |        "c1"
         |      ]
         |    ]
         |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "notCreated": {
           |                    "4f29": {
           |                        "type": "invalidArguments",
           |                        "description": "'/types(0)' property in PushSubscription object is not valid: Unknown typeName invalid"
           |                    }
           |                }
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def setMethodShouldNotCreatedWhenDeviceClientIdExists(pushServer: ClientAndServer): Unit = {
    val request: String =
      s"""{
        |    "using": ["urn:ietf:params:jmap:core"],
        |    "methodCalls": [
        |      [
        |        "PushSubscription/set",
        |        {
        |            "create": {
        |                "4f29": {
        |                  "deviceClientId": "a889-ffea-910",
        |                  "url": "${getPushServerUrl(pushServer)}",
        |                  "types": ["Mailbox"]
        |                }
        |              }
        |        },
        |        "c1"
        |      ]
        |    ]
        |  }""".stripMargin

    `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "notCreated": {
           |                    "4f29": {
           |                        "type": "invalidArguments",
           |                        "description": "`a889-ffea-910` deviceClientId must be unique"
           |                    }
           |                }
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def setMethodShouldAcceptValidExpiresProperty(pushServer: ClientAndServer): Unit = {
    val request: String =
      s"""{
         |    "using": ["urn:ietf:params:jmap:core"],
         |    "methodCalls": [
         |      [
         |        "PushSubscription/set",
         |        {
         |            "create": {
         |                "4f29": {
         |                  "deviceClientId": "a889-ffea-910",
         |                  "url": "${getPushServerUrl(pushServer)}",
         |                  "expires": "${UTCDate(ZonedDateTime.now().plusDays(1)).asUTC.format(TIME_FORMATTER)}",
         |                  "types": ["Mailbox"]
         |                }
         |              }
         |        },
         |        "c1"
         |      ]
         |    ]
         |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "created": {
           |                    "4f29": {
           |                        "id": "$${json-unit.ignore}",
           |                        "expires": "$${json-unit.ignore}"
           |                    }
           |                }
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def setMethodShouldCreatedWhenValidRequest(pushServer: ClientAndServer): Unit = {
    val request: String =
      s"""{
        |    "using": ["urn:ietf:params:jmap:core"],
        |    "methodCalls": [
        |      [
        |        "PushSubscription/set",
        |        {
        |            "create": {
        |                "4f29": {
        |                  "deviceClientId": "a889-ffea-910",
        |                  "url": "${getPushServerUrl(pushServer)}",
        |                  "types": ["Mailbox"]
        |                }
        |              }
        |        },
        |        "c1"
        |      ]
        |    ]
        |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "created": {
           |                    "4f29": {
           |                        "id": "$${json-unit.ignore}",
           |                        "expires": "$${json-unit.ignore}"
           |                    }
           |                }
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def setMethodShouldCreatedSeveralValidCreationRequest(pushServer: ClientAndServer): Unit = {
    val request: String =
      s"""{
        |    "using": ["urn:ietf:params:jmap:core"],
        |    "methodCalls": [
        |      [
        |        "PushSubscription/set",
        |        {
        |            "create": {
        |                "4f28": {
        |                  "deviceClientId": "a889-ffea-910",
        |                  "url": "${getPushServerUrl(pushServer)}",
        |                  "types": ["Mailbox"]
        |                },
        |                "4f29": {
        |                  "deviceClientId": "a889-ffea-912",
        |                  "url": "${getPushServerUrl(pushServer)}",
        |                  "types": ["Email"]
        |                }
        |              }
        |        },
        |        "c1"
        |      ]
        |    ]
        |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "created": {
           |                    "4f28": {
           |                        "id": "$${json-unit.ignore}",
           |                        "expires": "$${json-unit.ignore}"
           |                    },
           |                    "4f29": {
           |                        "id": "$${json-unit.ignore}",
           |                        "expires": "$${json-unit.ignore}"
           |                    }
           |                }
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def setMethodShouldSuccessWhenMixCase(pushServer: ClientAndServer): Unit = {
    val request: String =
      s"""{
        |    "using": ["urn:ietf:params:jmap:core"],
        |    "methodCalls": [
        |      [
        |        "PushSubscription/set",
        |        {
        |            "create": {
        |                "4f28": {
        |                  "deviceClientId": "a889-ffea-910",
        |                  "url": "${getPushServerUrl(pushServer)}",
        |                  "types": ["Mailbox"]
        |                },
        |                "4f29": {
        |                  "deviceClientId": "a889-ffea-912",
        |                  "url": "${getPushServerUrl(pushServer)}",
        |                  "types": ["invalid"]
        |                }
        |              }
        |        },
        |        "c1"
        |      ]
        |    ]
        |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "created": {
           |                    "4f28": {
           |                        "id": "$${json-unit.ignore}",
           |                        "expires": "$${json-unit.ignore}"
           |                    }
           |                },
           |                "notCreated": {
           |                    "4f29": {
           |                        "type": "invalidArguments",
           |                        "description": "'/types(0)' property in PushSubscription object is not valid: Unknown typeName invalid"
           |                    }
           |                }
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def updateShouldValidateVerificationCode(server: GuiceJamesServer): Unit = {
    val probe = server.getProbe(classOf[PushSubscriptionProbe])
    val pushSubscription = probe
      .createPushSubscription(username = BOB,
        url = PushSubscriptionServerURL(new URL("https://example.com/push/?device=X8980fc&client=12c6d086")),
        deviceId = DeviceClientId("12c6d086"),
        types = Seq(MailboxTypeName, EmailDeliveryTypeName, EmailTypeName))

    val request: String =
      s"""{
        |    "using": ["urn:ietf:params:jmap:core"],
        |    "methodCalls": [
        |      [
        |        "PushSubscription/set",
        |        {
        |            "update": {
        |                "${pushSubscription.id.serialise}": {
        |                  "verificationCode": "${pushSubscription.verificationCode.value}"
        |                }
        |              }
        |        },
        |        "c1"
        |      ]
        |    ]
        |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "updated": {
           |                    "${pushSubscription.id.serialise}": {}
           |                }
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)

    assertThat(probe.retrievePushSubscription(BOB, pushSubscription.id).validated).isTrue
  }

  @Test
  def setMethodShouldRejectInvalidKey(): Unit = {
    val request: String =
      """{
        |    "using": ["urn:ietf:params:jmap:core"],
        |    "methodCalls": [
        |      [
        |        "PushSubscription/set",
        |        {
        |            "create": {
        |                "4f29": {
        |                  "deviceClientId": "a889-ffea-910",
        |                  "url": "https://example.com/push/?device=X8980fc&client=12c6d086",
        |                  "types": ["Mailbox"],
        |                  "keys": {
        |                    "p256dh": "QmFkIGtleQo",
        |                    "auth": "YXV0aCBzZWNyZXQK"
        |                  }
        |                }
        |              }
        |        },
        |        "c1"
        |      ]
        |    ]
        |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "notCreated": {
           |                    "4f29": {
           |                        "type": "invalidArguments",
           |                        "description": "java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException: null"
           |                    }
           |                }
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def setMethodShouldAcceptValidKey(pushServer: ClientAndServer): Unit = {
    val request: String =
      s"""{
        |    "using": ["urn:ietf:params:jmap:core"],
        |    "methodCalls": [
        |      [
        |        "PushSubscription/set",
        |        {
        |            "create": {
        |                "4f29": {
        |                  "deviceClientId": "a889-ffea-910",
        |                  "url": "${getPushServerUrl(pushServer)}",
        |                  "types": ["Mailbox"],
        |                  "keys": {
        |                    "p256dh": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5ozzvKUAB7GIfJ44eG-sxEcjT1O2jtk9QVD-MzFOH988CAPlSdkitm16NsMxUWksq6qGwu-r6zT7GCM9oGPXtQ==",
        |                    "auth": "Z7B0LmM6iTZD85EWtNRwIg=="
        |                  }
        |                }
        |              }
        |        },
        |        "c1"
        |      ]
        |    ]
        |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "created": {
           |                    "4f29": {
           |                        "id": "$${json-unit.ignore}",
           |                        "expires": "$${json-unit.ignore}"
           |                    }
           |                }
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def updateMixed(server: GuiceJamesServer): Unit = {
    val probe = server.getProbe(classOf[PushSubscriptionProbe])
    val pushSubscription1 = probe
      .createPushSubscription(username = BOB,
        url = PushSubscriptionServerURL(new URL("https://example.com/push/?device=X8980fc&client=12c6d086")),
        deviceId = DeviceClientId("12c6d081"),
        types = Seq(MailboxTypeName, EmailDeliveryTypeName, EmailTypeName))
    val pushSubscription2 = probe
      .createPushSubscription(username = BOB,
        url = PushSubscriptionServerURL(new URL("https://example.com/push/?device=X8980fc&client=12c6d086")),
        deviceId = DeviceClientId("12c6d082"),
        types = Seq(MailboxTypeName, EmailDeliveryTypeName, EmailTypeName))
    val pushSubscription3 = probe
      .createPushSubscription(username = BOB,
        url = PushSubscriptionServerURL(new URL("https://example.com/push/?device=X8980fc&client=12c6d086")),
        deviceId = DeviceClientId("12c6d083"),
        types = Seq(MailboxTypeName, EmailDeliveryTypeName, EmailTypeName))
    val pushSubscription4 = probe
      .createPushSubscription(username = BOB,
        url = PushSubscriptionServerURL(new URL("https://example.com/push/?device=X8980fc&client=12c6d086")),
        deviceId = DeviceClientId("12c6d084"),
        types = Seq(MailboxTypeName, EmailDeliveryTypeName, EmailTypeName))

    val request: String =
      s"""{
        |    "using": ["urn:ietf:params:jmap:core"],
        |    "methodCalls": [
        |      [
        |        "PushSubscription/set",
        |        {
        |            "update": {
        |                "${pushSubscription1.id.serialise}": {
        |                  "verificationCode": "${pushSubscription1.verificationCode.value}"
        |                },
        |                "${pushSubscription2.id.serialise}": {
        |                  "verificationCode": "wrong"
        |                },
        |                "${pushSubscription3.id.serialise}": {
        |                  "verificationCode": "${pushSubscription3.verificationCode.value}"
        |                },
        |                "${pushSubscription4.id.serialise}": {
        |                  "verificationCode": "wrongAgain"
        |                }
        |              }
        |        },
        |        "c1"
        |      ]
        |    ]
        |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "updated": {
           |                    "${pushSubscription1.id.serialise}": {},
           |                    "${pushSubscription3.id.serialise}": {}
           |                },
           |                "notUpdated": {
           |                    "${pushSubscription2.id.serialise}": {
           |                        "type": "invalidProperties",
           |                        "description": "Wrong verification code",
           |                        "properties": [
           |                            "verificationCode"
           |                        ]
           |                    },
           |                    "${pushSubscription4.id.serialise}": {
           |                        "type": "invalidProperties",
           |                        "description": "Wrong verification code",
           |                        "properties": [
           |                            "verificationCode"
           |                        ]
           |                    }
           |                }
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)

    SoftAssertions.assertSoftly(softly => {
      softly.assertThat(probe.retrievePushSubscription(BOB, pushSubscription1.id).validated).isTrue
      softly.assertThat(probe.retrievePushSubscription(BOB, pushSubscription2.id).validated).isFalse
      softly.assertThat(probe.retrievePushSubscription(BOB, pushSubscription3.id).validated).isTrue
      softly.assertThat(probe.retrievePushSubscription(BOB, pushSubscription4.id).validated).isFalse
    })
  }

  @Test
  def updateShouldNotValidateVerificationCodeWhenWrong(server: GuiceJamesServer): Unit = {
    val probe = server.getProbe(classOf[PushSubscriptionProbe])
    val pushSubscription = probe
      .createPushSubscription(username = BOB,
        url = PushSubscriptionServerURL(new URL("https://example.com/push/?device=X8980fc&client=12c6d086")),
        deviceId = DeviceClientId("12c6d086"),
        types = Seq(MailboxTypeName, EmailDeliveryTypeName, EmailTypeName))

    val request: String =
      s"""{
        |    "using": ["urn:ietf:params:jmap:core"],
        |    "methodCalls": [
        |      [
        |        "PushSubscription/set",
        |        {
        |            "update": {
        |                "${pushSubscription.id.serialise}": {
        |                  "verificationCode": "wrong"
        |                }
        |              }
        |        },
        |        "c1"
        |      ]
        |    ]
        |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "notUpdated": {
           |                    "${pushSubscription.id.serialise}": {
           |                        "type": "invalidProperties",
           |                        "description": "Wrong verification code",
           |                        "properties": [
           |                            "verificationCode"
           |                        ]
           |                    }
           |                }
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)

    assertThat(probe.retrievePushSubscription(BOB, pushSubscription.id).validated).isFalse
  }

  @Test
  def updateValidExpiresShouldSucceed(server: GuiceJamesServer): Unit = {
    val probe = server.getProbe(classOf[PushSubscriptionProbe])
    val pushSubscription = probe
      .createPushSubscription(username = BOB,
        url = PushSubscriptionServerURL(new URL("https://example.com/push/?device=X8980fc&client=12c6d086")),
        deviceId = DeviceClientId("12c6d086"),
        types = Seq(MailboxTypeName, EmailDeliveryTypeName, EmailTypeName))

    val validExpiresString = UTCDate(ZonedDateTime.now().plusDays(1)).asUTC.format(TIME_FORMATTER)
    val request: String =
      s"""{
         |    "using": ["urn:ietf:params:jmap:core"],
         |    "methodCalls": [
         |      [
         |        "PushSubscription/set",
         |        {
         |            "update": {
         |                "${pushSubscription.id.serialise}": {
         |                 "expires": "$validExpiresString"
         |                }
         |              }
         |        },
         |        "c1"
         |      ]
         |    ]
         |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "updated": {
           |                    "${pushSubscription.id.serialise}": {}
           |                }
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)

    assertThat(probe.retrievePushSubscription(BOB, pushSubscription.id)
      .expires.value.format(TIME_FORMATTER))
      .isEqualTo(validExpiresString)
  }

  @Test
  def updateInvalidExpiresStringShouldFail(server: GuiceJamesServer): Unit = {
    val probe = server.getProbe(classOf[PushSubscriptionProbe])
    val pushSubscription = probe
      .createPushSubscription(username = BOB,
        url = PushSubscriptionServerURL(new URL("https://example.com/push/?device=X8980fc&client=12c6d086")),
        deviceId = DeviceClientId("12c6d086"),
        types = Seq(MailboxTypeName, EmailDeliveryTypeName, EmailTypeName))

    val invalidExpiresString = "whatever"
    val request: String =
      s"""{
         |    "using": ["urn:ietf:params:jmap:core"],
         |    "methodCalls": [
         |      [
         |        "PushSubscription/set",
         |        {
         |            "update": {
         |                "${pushSubscription.id.serialise}": {
         |                 "expires": "$invalidExpiresString"
         |                }
         |              }
         |        },
         |        "c1"
         |      ]
         |    ]
         |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |	"sessionState": "${SESSION_STATE.value}",
           |	"methodResponses": [
           |		[
           |			"PushSubscription/set",
           |			{
           |				"notUpdated": {
           |					"${pushSubscription.id.serialise}": {
           |						"type": "invalidArguments",
           |						"description": "This string can not be parsed to UTCDate",
           |						"properties": ["expires"]
           |					}
           |				}
           |			},
           |			"c1"
           |		]
           |	]
           |}""".stripMargin)
  }

  @Test
  def updateWithBiggerExpiresThanServerLimitShouldSetToServerLimitAndExplicitlyReturned(server: GuiceJamesServer): Unit = {
    val probe = server.getProbe(classOf[PushSubscriptionProbe])
    val pushSubscription = probe
      .createPushSubscription(username = BOB,
        url = PushSubscriptionServerURL(new URL("https://example.com/push/?device=X8980fc&client=12c6d086")),
        deviceId = DeviceClientId("12c6d086"),
        types = Seq(MailboxTypeName, EmailDeliveryTypeName, EmailTypeName))

    val biggerExpiresString = UTCDate(ZonedDateTime.now().plusDays(10)).asUTC.format(TIME_FORMATTER)
    val request: String =
      s"""{
         |    "using": ["urn:ietf:params:jmap:core"],
         |    "methodCalls": [
         |      [
         |        "PushSubscription/set",
         |        {
         |            "update": {
         |                "${pushSubscription.id.serialise}": {
         |                 "expires": "$biggerExpiresString"
         |                }
         |              }
         |        },
         |        "c1"
         |      ]
         |    ]
         |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    val fixedExpires = probe.retrievePushSubscription(BOB, pushSubscription.id)
      .expires.value.format(TIME_FORMATTER)

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "updated": {
           |                    "${pushSubscription.id.serialise}": {
           |                        "expires": "$fixedExpires"
           |                    }
           |                }
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def updateOutdatedExpiresShouldFail(server: GuiceJamesServer): Unit = {
    val probe = server.getProbe(classOf[PushSubscriptionProbe])
    val pushSubscription = probe
      .createPushSubscription(username = BOB,
        url = PushSubscriptionServerURL(new URL("https://example.com/push/?device=X8980fc&client=12c6d086")),
        deviceId = DeviceClientId("12c6d086"),
        types = Seq(MailboxTypeName, EmailDeliveryTypeName, EmailTypeName))

    val invalidExpiresString = UTCDate(ZonedDateTime.now().minusDays(1)).asUTC.format(TIME_FORMATTER)
    val request: String =
      s"""{
         |    "using": ["urn:ietf:params:jmap:core"],
         |    "methodCalls": [
         |      [
         |        "PushSubscription/set",
         |        {
         |            "update": {
         |                "${pushSubscription.id.serialise}": {
         |                 "expires": "$invalidExpiresString"
         |                }
         |              }
         |        },
         |        "c1"
         |      ]
         |    ]
         |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |	"sessionState": "${SESSION_STATE.value}",
           |	"methodResponses": [
           |		[
           |			"PushSubscription/set",
           |			{
           |				"notUpdated": {
           |					"${pushSubscription.id.serialise}": {
           |						"type": "invalidArguments",
           |						"description": "`$invalidExpiresString` expires must be greater than now",
           |						"properties": ["expires"]
           |					}
           |				}
           |			},
           |			"c1"
           |		]
           |	]
           |}""".stripMargin)
  }

  @Test
  def updateShouldFailWhenUnknownProperty(server: GuiceJamesServer): Unit = {
    val probe = server.getProbe(classOf[PushSubscriptionProbe])
    val pushSubscription = probe
      .createPushSubscription(username = BOB,
        url = PushSubscriptionServerURL(new URL("https://example.com/push/?device=X8980fc&client=12c6d086")),
        deviceId = DeviceClientId("12c6d086"),
        types = Seq(MailboxTypeName, EmailDeliveryTypeName, EmailTypeName))

    val request: String =
      s"""{
        |    "using": ["urn:ietf:params:jmap:core"],
        |    "methodCalls": [
        |      [
        |        "PushSubscription/set",
        |        {
        |            "update": {
        |                "${pushSubscription.id.serialise}": {
        |                  "unknown": "whatever"
        |                }
        |              }
        |        },
        |        "c1"
        |      ]
        |    ]
        |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "notUpdated": {
           |                    "${pushSubscription.id.serialise}": {
           |                        "description":"unknown property do not exist thus cannot be updated",
           |                        "properties":["unknown"],
           |                        "type":"invalidArguments"
           |                    }
           |                }
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)

    assertThat(probe.retrievePushSubscription(BOB, pushSubscription.id).validated).isFalse
  }

  @Test
  def updateShouldFailWhenInvalidId(): Unit = {
    val request: String =
      s"""{
        |    "using": ["urn:ietf:params:jmap:core"],
        |    "methodCalls": [
        |      [
        |        "PushSubscription/set",
        |        {
        |            "update": {
        |                "bad": {
        |                  "verificationCode": "anyValue"
        |                }
        |              }
        |        },
        |        "c1"
        |      ]
        |    ]
        |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "notUpdated": {
           |                    "bad": {
           |                        "type": "invalidArguments",
           |                        "description": "Invalid UUID string: bad"
           |                    }
           |                }
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def updateShouldFailWhenNotFound(): Unit = {
    val id = UUID.randomUUID().toString
    val request: String =
      s"""{
        |    "using": ["urn:ietf:params:jmap:core"],
        |    "methodCalls": [
        |      [
        |        "PushSubscription/set",
        |        {
        |            "update": {
        |                "$id": {
        |                  "verificationCode": "anyValue"
        |                }
        |              }
        |        },
        |        "c1"
        |      ]
        |    ]
        |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "notUpdated": {
           |                    "$id": {
           |                        "type": "notFound",
           |                        "description": null
           |                    }
           |                }
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def setMethodCreateShouldCallVerificationToPushServer(pushServer: ClientAndServer): Unit = {
    val request: String =
      s"""{
         |    "using": ["urn:ietf:params:jmap:core"],
         |    "methodCalls": [
         |      [
         |        "PushSubscription/set",
         |        {
         |            "create": {
         |                "4f29": {
         |                  "deviceClientId": "a889-ffea-910",
         |                  "url": "${getPushServerUrl(pushServer)}",
         |                  "types": ["Mailbox"]
         |                }
         |              }
         |        },
         |        "c1"
         |      ]
         |    ]
         |  }""".stripMargin

    val pushSubscriptionId: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .extract()
      .jsonPath()
      .get("methodResponses[0][1].created.4f29.id")

    pushServer.verify(HttpRequest.request()
      .withPath("/subscribe")
      .withBody(json(s"""{
                        |    "@type": "PushVerification",
                        |    "pushSubscriptionId": "$pushSubscriptionId",
                        |    "verificationCode": "$${json-unit.any-string}"
                        |}""".stripMargin)),
      VerificationTimes.atLeast(1))
  }

  @Test
  def setMethodCreateShouldNotCreatedWhenCallVerificationToPushServerHasError(pushServer: ClientAndServer): Unit = {
    pushServer
      .when(HttpRequest.request
        .withPath("/invalid")
        .withMethod("POST"))
      .respond(HttpResponse.response
        .withStatusCode(500))

    val pushServerUrl = s"http://127.0.0.1:${pushServer.getLocalPort}/invalid"
    val request: String =
      s"""{
         |    "using": ["urn:ietf:params:jmap:core"],
         |    "methodCalls": [
         |      [
         |        "PushSubscription/set",
         |        {
         |            "create": {
         |                "4f29": {
         |                  "deviceClientId": "a889-ffea-910",
         |                  "url": "$pushServerUrl",
         |                  "types": ["Mailbox"]
         |                }
         |              }
         |        },
         |        "c1"
         |      ]
         |    ]
         |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "notCreated": {
           |                    "4f29": {
           |                        "type": "serverFail",
           |                        "description": "Error when call to Push Server. "
           |                    }
           |                }
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def setMethodCreateShouldNotSaveSubscriptionEntryWhenCallVerificationToPushServerHasError(pushServer: ClientAndServer): Unit = {
    pushServer
      .when(HttpRequest.request
        .withPath("/invalid")
        .withMethod("POST"))
      .respond(HttpResponse.response
        .withStatusCode(500))

    val deviceId: String = "a889-ffea-910"
    val invalidPushServerUrl: String = s"http://127.0.0.1:${pushServer.getLocalPort}/invalid"

    `given`
      .body(
        s"""{
           |    "using": ["urn:ietf:params:jmap:core"],
           |    "methodCalls": [
           |      [
           |        "PushSubscription/set",
           |        {
           |            "create": {
           |                "4f29": {
           |                  "deviceClientId": "$deviceId",
           |                  "url": "$invalidPushServerUrl",
           |                  "types": ["Mailbox"]
           |                }
           |              }
           |        },
           |        "c1"
           |      ]
           |    ]
           |  }""".stripMargin)
    .when
      .post
    .`then`
      .statusCode(SC_OK)

    val response: String = `given`
      .body(
        s"""{
           |    "using": ["urn:ietf:params:jmap:core"],
           |    "methodCalls": [
           |      [
           |        "PushSubscription/set",
           |        {
           |            "create": {
           |                "4f29": {
           |                  "deviceClientId": "$deviceId",
           |                  "url": "${getPushServerUrl(pushServer)}",
           |                  "types": ["Mailbox"]
           |                }
           |              }
           |        },
           |        "c1"
           |      ]
           |    ]
           |  }""".stripMargin)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "created": {
           |                    "4f29": {
           |                        "id": "$${json-unit.ignore}",
           |                        "expires": "$${json-unit.ignore}"
           |                    }
           |                }
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def setMethodShouldAcceptValidKeys(pushServer: ClientAndServer) : Unit = {
    val uaKeyPair: KeyPair = EllipticCurves.generateKeyPair(CurveType.NIST_P256)
    val uaPublicKey: ECPublicKey = uaKeyPair.getPublic.asInstanceOf[ECPublicKey]
    val authSecret: Array[Byte] = Random.randBytes(16)

    val p256dh: String = Base64.getUrlEncoder.encodeToString(uaPublicKey.getEncoded)
    val auth: String = Base64.getUrlEncoder.encodeToString(authSecret)

    val request: String =
      s"""{
         |    "using": ["urn:ietf:params:jmap:core"],
         |    "methodCalls": [
         |      [
         |        "PushSubscription/set",
         |        {
         |            "create": {
         |                "4f29": {
         |                  "deviceClientId": "a889-ffea-910",
         |                  "url": "${getPushServerUrl(pushServer)}",
         |                  "types": ["Mailbox"],
         |                  "keys": {
         |                    "p256dh": "$p256dh",
         |                    "auth": "$auth"
         |                  }
         |                }
         |              }
         |        },
         |        "c1"
         |      ]
         |    ]
         |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "created": {
           |                    "4f29": {
           |                        "id": "$${json-unit.ignore}",
           |                        "expires": "$${json-unit.ignore}"
           |                    }
           |                }
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def bodyRequestToPushServerShouldBeEncryptedWhenClientAssignEncryptionKeys(pushServer: ClientAndServer) : Unit = {
    val uaKeyPair: KeyPair = EllipticCurves.generateKeyPair(CurveType.NIST_P256)
    val uaPublicKey: ECPublicKey = uaKeyPair.getPublic.asInstanceOf[ECPublicKey]
    val authSecret: Array[Byte] = "secret123secret1".getBytes

    val p256dh: String = Base64.getUrlEncoder.encodeToString(uaPublicKey.getEncoded)
    val auth: String = Base64.getUrlEncoder.encodeToString(authSecret)

    val request: String =
      s"""{
         |    "using": ["urn:ietf:params:jmap:core"],
         |    "methodCalls": [
         |      [
         |        "PushSubscription/set",
         |        {
         |            "create": {
         |                "4f29": {
         |                  "deviceClientId": "a889-ffea-910",
         |                  "url": "${getPushServerUrl(pushServer)}",
         |                  "types": ["Mailbox"],
         |                  "keys": {
         |                    "p256dh": "$p256dh",
         |                    "auth": "$auth"
         |                  }
         |                }
         |              }
         |        },
         |        "c1"
         |      ]
         |    ]
         |  }""".stripMargin

    `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)

    pushServer.verify(HttpRequest.request()
      .withPath("/subscribe")
      .withBody(not(json(
        s"""{
           |    "@type": "PushVerification",
           |    "pushSubscriptionId": "$${json-unit.any-string}",
           |    "verificationCode": "$${json-unit.any-string}"
           |}""".stripMargin))),
      VerificationTimes.atLeast(1))
  }

  @Test
  def destroyShouldSucceed(server: GuiceJamesServer): Unit = {
    val probe = server.getProbe(classOf[PushSubscriptionProbe])
    val pushSubscription = probe
      .createPushSubscription(username = BOB,
        url = PushSubscriptionServerURL(new URL("https://example.com/push/?device=X8980fc&client=12c6d086")),
        deviceId = DeviceClientId("12c6d086"),
        types = Seq(MailboxTypeName, EmailDeliveryTypeName, EmailTypeName))

    val request: String =
      s"""{
         |    "using": ["urn:ietf:params:jmap:core"],
         |    "methodCalls": [
         |      [
         |        "PushSubscription/set",
         |        {
         |            "destroy": ["${pushSubscription.id.value.toString}"]
         |        },
         |        "c1"
         |      ]
         |    ]
         |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "destroyed": ["${pushSubscription.id.value.toString}"]
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)

    assertThat(probe.retrievePushSubscription(BOB, pushSubscription.id)).isNull()
  }

  @Test
  def destroyShouldFailWhenInvalidId(): Unit = {
    val request: String =
      s"""{
         |    "using": ["urn:ietf:params:jmap:core"],
         |    "methodCalls": [
         |      [
         |        "PushSubscription/set",
         |        {
         |            "destroy": ["invalid"]
         |        },
         |        "c1"
         |      ]
         |    ]
         |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "notDestroyed": {
           |                    "invalid": {
           |                        "type": "invalidArguments",
           |                        "description": "invalid is not a PushSubscriptionId: Invalid UUID string: invalid"
           |                    }
           |                }
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def destroyShouldNotFailWhenUnknownId(): Unit = {
    val id = UUID.randomUUID().toString

    val request: String =
      s"""{
         |    "using": ["urn:ietf:params:jmap:core"],
         |    "methodCalls": [
         |      [
         |        "PushSubscription/set",
         |        {
         |            "destroy": ["$id"]
         |        },
         |        "c1"
         |      ]
         |    ]
         |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "destroyed":["$id"]
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }

  @Test
  def destroyShouldHandleMixedCases(server: GuiceJamesServer): Unit = {
    val probe = server.getProbe(classOf[PushSubscriptionProbe])
    val pushSubscription1 = probe
      .createPushSubscription(username = BOB,
        url = PushSubscriptionServerURL(new URL("https://example.com/push/?device=X8980fc&client=12c6d086")),
        deviceId = DeviceClientId("12c6d086"),
        types = Seq(MailboxTypeName, EmailDeliveryTypeName, EmailTypeName))
    val pushSubscription2 = probe
      .createPushSubscription(username = BOB,
        url = PushSubscriptionServerURL(new URL("https://example.com/push/?device=X8980fc&client=12c6d087")),
        deviceId = DeviceClientId("12c6d087"),
        types = Seq(MailboxTypeName, EmailDeliveryTypeName, EmailTypeName))

    val request: String =
      s"""{
         |    "using": ["urn:ietf:params:jmap:core"],
         |    "methodCalls": [
         |      [
         |        "PushSubscription/set",
         |        {
         |            "destroy": ["${pushSubscription1.id.value.toString}", "${pushSubscription2.id.value.toString}", "invalid"]
         |        },
         |        "c1"
         |      ]
         |    ]
         |  }""".stripMargin

    val response: String = `given`
      .body(request)
    .when
      .post
    .`then`
      .statusCode(SC_OK)
      .contentType(JSON)
      .extract
      .body
      .asString

    assertThatJson(response)
      .when(net.javacrumbs.jsonunit.core.Option.IGNORING_ARRAY_ORDER)
      .isEqualTo(
        s"""{
           |    "sessionState": "${SESSION_STATE.value}",
           |    "methodResponses": [
           |        [
           |            "PushSubscription/set",
           |            {
           |                "destroyed": [
           |                    "${pushSubscription1.id.value.toString}",
           |                    "${pushSubscription2.id.value.toString}"
           |                ],
           |                "notDestroyed": {
           |                    "invalid": {
           |                        "type": "invalidArguments",
           |                        "description": "invalid is not a PushSubscriptionId: Invalid UUID string: invalid"
           |                    }
           |                }
           |            },
           |            "c1"
           |        ]
           |    ]
           |}""".stripMargin)
  }
}
