blob: 1be1b48e0f8d74a1a87395608e06bdfa239357fc [file] [log] [blame]
/****************************************************************
* 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 java.time.ZonedDateTime
import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
import io.restassured.RestAssured.{`given`, requestSpecification}
import io.restassured.http.ContentType.JSON
import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
import org.apache.http.HttpStatus.SC_OK
import org.apache.james.GuiceJamesServer
import org.apache.james.jmap.JmapGuiceProbe
import org.apache.james.jmap.api.model.AccountId
import org.apache.james.jmap.core.ResponseObject.SESSION_STATE
import org.apache.james.jmap.core.UuidState.INSTANCE
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.VacationResponseGetMethodContract.VACATION_RESPONSE
import org.apache.james.jmap.rfc8621.contract.tags.CategoryTags
import org.apache.james.utils.DataProbeImpl
import org.apache.james.vacation.api.VacationPatch
import org.junit.jupiter.api.{BeforeEach, Tag, Test}
object VacationResponseGetMethodContract {
private val VACATION_RESPONSE: VacationPatch =
VacationPatch.builder
.isEnabled(true)
.fromDate(ZonedDateTime.parse("2014-09-30T14:10:00+02:00"))
.toDate(ZonedDateTime.parse("2016-04-15T11:56:32.224+07:00[Asia/Vientiane]"))
.subject("On vacation...")
.textBody("Test explaining my vacations")
.htmlBody("<b>Test explaining my vacations</b>")
.build
}
trait VacationResponseGetMethodContract {
@BeforeEach
def setUp(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[DataProbeImpl])
.fluent
.addDomain(DOMAIN.asString)
.addUser(BOB.asString, BOB_PASSWORD)
requestSpecification = baseRequestSpecBuilder(server)
.setAuth(authScheme(UserCredential(BOB, BOB_PASSWORD)))
.build
}
@Test
def vacationResponseGetShouldFailWhenWrongAccountId(server: GuiceJamesServer): Unit = {
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail",
| "urn:ietf:params:jmap:vacationresponse"],
| "methodCalls": [[
| "VacationResponse/get",
| {
| "accountId": "unknownAccountId",
| "ids": null
| },
| "c1"]]
|}""".stripMargin
val response = `given`
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
.body(request)
.when
.post
.`then`
.log().ifValidationFails()
.statusCode(SC_OK)
.contentType(JSON)
.extract
.body
.asString
assertThatJson(response).isEqualTo(
s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [
| ["error", {
| "type": "accountNotFound"
| }, "c1"]
| ]
|}""".stripMargin)
}
@Test
@Tag(CategoryTags.BASIC_FEATURE)
def vacationResponseShouldBeDisabledByDefault(): Unit = {
val response = `given`
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
.body(s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail",
| "urn:ietf:params:jmap:vacationresponse"],
| "methodCalls": [[
| "VacationResponse/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "ids": null
| },
| "c1"]]
|}""".stripMargin)
.when
.post
.`then`
.statusCode(SC_OK)
.contentType(JSON)
.extract
.body
.asString
assertThatJson(response).isEqualTo(
s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [[
| "VacationResponse/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "state": "${INSTANCE.value}",
| "list": [
| {
| "id":"singleton",
| "isEnabled": false
| }
| ],
| "notFound": []
| },
| "c1"]]
|}""".stripMargin)
}
@Test
@Tag(CategoryTags.BASIC_FEATURE)
def vacationResponseShouldReturnStoredValue(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[JmapGuiceProbe])
.modifyVacation(AccountId.fromUsername(BOB), VACATION_RESPONSE)
val response = `given`
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
.body(s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail",
| "urn:ietf:params:jmap:vacationresponse"],
| "methodCalls": [[
| "VacationResponse/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "ids": null
| },
| "c1"]]
|}""".stripMargin)
.when
.post
.`then`
.statusCode(SC_OK)
.contentType(JSON)
.extract
.body
.asString
assertThatJson(response).isEqualTo(
s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [[
| "VacationResponse/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "state": "${INSTANCE.value}",
| "list": [
| {
| "id":"singleton",
| "isEnabled": true,
| "fromDate": "2014-09-30T12:10:00Z",
| "toDate": "2016-04-15T04:56:32Z",
| "subject": "On vacation...",
| "textBody": "Test explaining my vacations",
| "htmlBody": "<b>Test explaining my vacations</b>"
| }
| ],
| "notFound": []
| },
| "c1"]]
|}""".stripMargin)
}
@Test
def vacationResponseShouldReturnUnknownMethodWhenOmittingOneCapability(): Unit = {
val response = `given`
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
.body(s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "VacationResponse/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "ids": null
| },
| "c1"]]
|}""".stripMargin)
.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:vacationresponse"
| },
| "c1"]]
|}""".stripMargin)
}
@Test
def vacationResponseShouldReturnUnknownMethodWhenMissingAllCapability(): Unit = {
val response = `given`
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
.body(s"""{
| "using": [],
| "methodCalls": [[
| "VacationResponse/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "ids": null
| },
| "c1"]]
|}""".stripMargin)
.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, urn:ietf:params:jmap:vacationresponse"
| },
| "c1"]]
|}""".stripMargin)
}
@Test
def vacationResponseShouldReturnValidResponseWhenSingletonId(): Unit = {
val response = `given`
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
.body(s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail",
| "urn:ietf:params:jmap:vacationresponse"],
| "methodCalls": [[
| "VacationResponse/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "ids": ["singleton"]
| },
| "c1"]]
|}""".stripMargin)
.when
.post
.`then`
.statusCode(SC_OK)
.contentType(JSON)
.extract
.body
.asString
assertThatJson(response).isEqualTo(
s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [[
| "VacationResponse/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "state": "${INSTANCE.value}",
| "list": [
| {
| "id":"singleton",
| "isEnabled": false
| }
| ],
| "notFound": []
| },
| "c1"]]
|}""".stripMargin)
}
@Test
def vacationResponseShouldReturnNotFoundWhenIdNotSingleton(): Unit = {
val response = `given`
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
.body(s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail",
| "urn:ietf:params:jmap:vacationresponse"],
| "methodCalls": [[
| "VacationResponse/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "ids": ["random"]
| },
| "c1"]]
|}""".stripMargin)
.when
.post
.`then`
.statusCode(SC_OK)
.contentType(JSON)
.extract
.body
.asString
assertThatJson(response).isEqualTo(
s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [[
| "VacationResponse/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "state": "${INSTANCE.value}",
| "list": [],
| "notFound": ["random"]
| },
| "c1"]]
|}""".stripMargin)
}
@Test
def vacationResponseShouldReturnSingletonAndNotFoundIds(): Unit = {
val response = `given`
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
.body(s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail",
| "urn:ietf:params:jmap:vacationresponse"],
| "methodCalls": [[
| "VacationResponse/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "ids": ["random1", "singleton", "random2"]
| },
| "c1"]]
|}""".stripMargin)
.when
.post
.`then`
.statusCode(SC_OK)
.contentType(JSON)
.extract
.body
.asString
assertThatJson(response).isEqualTo(
s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [[
| "VacationResponse/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "state": "${INSTANCE.value}",
| "list": [
| {
| "id":"singleton",
| "isEnabled": false
| }
| ],
| "notFound": ["random1", "random2"]
| },
| "c1"]]
|}""".stripMargin)
}
@Test
def vacationResponseShouldReturnEmptyListWhenEmptyIdsArray(): Unit = {
val response = `given`
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
.body(s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail",
| "urn:ietf:params:jmap:vacationresponse"],
| "methodCalls": [[
| "VacationResponse/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "ids": []
| },
| "c1"]]
|}""".stripMargin)
.when
.post
.`then`
.statusCode(SC_OK)
.contentType(JSON)
.extract
.body
.asString
assertThatJson(response).isEqualTo(
s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [[
| "VacationResponse/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "state": "${INSTANCE.value}",
| "list": [],
| "notFound": []
| },
| "c1"]]
|}""".stripMargin)
}
@Test
def vacationResponseShouldFailWhenEmptyId(): Unit = {
val response = `given`
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
.body(s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail",
| "urn:ietf:params:jmap:vacationresponse"],
| "methodCalls": [[
| "VacationResponse/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "ids": [""]
| },
| "c1"]]
|}""".stripMargin)
.when
.post
.`then`
.statusCode(SC_OK)
.contentType(JSON)
.extract
.body
.asString
.stripMargin
assertThatJson(response)
// I gave up trying to understand the escape sequence after 1 hour +. (a Json in a JSON in a scala string)
// If somebody ants to give a shot to assert the description...
.whenIgnoringPaths("methodResponses[0][1].description")
.isEqualTo(
s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [[
| "error",
| {
| "type": "invalidArguments"
| },
| "c1"]]
|}""".stripMargin)
}
@Test
def vacationResponseShouldReturnAllPropertiesWhenNull(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[JmapGuiceProbe])
.modifyVacation(AccountId.fromUsername(BOB), VACATION_RESPONSE)
val response = `given`
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
.body(s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail",
| "urn:ietf:params:jmap:vacationresponse"],
| "methodCalls": [[
| "VacationResponse/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "ids": null,
| "properties": null
| },
| "c1"]]
|}""".stripMargin)
.when
.post
.`then`
.statusCode(SC_OK)
.contentType(JSON)
.extract
.body
.asString
assertThatJson(response).isEqualTo(
s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [[
| "VacationResponse/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "state": "${INSTANCE.value}",
| "list": [
| {
| "id":"singleton",
| "isEnabled": true,
| "fromDate": "2014-09-30T12:10:00Z",
| "toDate": "2016-04-15T04:56:32Z",
| "subject": "On vacation...",
| "textBody": "Test explaining my vacations",
| "htmlBody": "<b>Test explaining my vacations</b>"
| }
| ],
| "notFound": []
| },
| "c1"]]
|}""".stripMargin)
}
@Test
def vacationResponseShouldReturnIdWhenNoPropertiesRequested(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[JmapGuiceProbe])
.modifyVacation(AccountId.fromUsername(BOB), VACATION_RESPONSE)
val response = `given`
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
.body(s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail",
| "urn:ietf:params:jmap:vacationresponse"],
| "methodCalls": [[
| "VacationResponse/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "ids": null,
| "properties": []
| },
| "c1"]]
|}""".stripMargin)
.when
.post
.`then`
.statusCode(SC_OK)
.contentType(JSON)
.extract
.body
.asString
assertThatJson(response).isEqualTo(
s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [[
| "VacationResponse/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "state": "${INSTANCE.value}",
| "list": [
| {
| "id":"singleton"
| }
| ],
| "notFound": []
| },
| "c1"]]
|}""".stripMargin)
}
@Test
def vacationResponseShouldReturnOnlyRequestedProperties(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[JmapGuiceProbe])
.modifyVacation(AccountId.fromUsername(BOB), VACATION_RESPONSE)
val response = `given`
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
.body(s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail",
| "urn:ietf:params:jmap:vacationresponse"],
| "methodCalls": [[
| "VacationResponse/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "ids": null,
| "properties": ["id", "subject"]
| },
| "c1"]]
|}""".stripMargin)
.when
.post
.`then`
.statusCode(SC_OK)
.contentType(JSON)
.extract
.body
.asString
assertThatJson(response).isEqualTo(
s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [[
| "VacationResponse/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "state": "${INSTANCE.value}",
| "list": [
| {
| "id":"singleton",
| "subject": "On vacation..."
| }
| ],
| "notFound": []
| },
| "c1"]]
|}""".stripMargin)
}
@Test
def vacationResponseShouldAlwaysReturnIdEvenIfNotRequestedInProperties(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[JmapGuiceProbe])
.modifyVacation(AccountId.fromUsername(BOB), VACATION_RESPONSE)
val response = `given`
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
.body(s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail",
| "urn:ietf:params:jmap:vacationresponse"],
| "methodCalls": [[
| "VacationResponse/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "ids": null,
| "properties": ["subject"]
| },
| "c1"]]
|}""".stripMargin)
.when
.post
.`then`
.statusCode(SC_OK)
.contentType(JSON)
.extract
.body
.asString
assertThatJson(response).isEqualTo(
s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [[
| "VacationResponse/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "state": "${INSTANCE.value}",
| "list": [
| {
| "id":"singleton",
| "subject": "On vacation..."
| }
| ],
| "notFound": []
| },
| "c1"]]
|}""".stripMargin)
}
@Test
def vacationResponseShouldReturnInvalidArgumentsErrorWhenInvalidProperty(): Unit = {
val response = `given`
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
.body(s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail",
| "urn:ietf:params:jmap:vacationresponse"],
| "methodCalls": [[
| "VacationResponse/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "ids": null,
| "properties": ["invalidProperty"]
| },
| "c1"]]
|}""".stripMargin)
.when
.post
.`then`
.statusCode(SC_OK)
.contentType(JSON)
.extract
.body
.asString
.stripMargin
assertThatJson(response).isEqualTo(
s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [[
| "error",
| {
| "type": "invalidArguments",
| "description": "The following properties [invalidProperty] do not exist."
| },
| "c1"]]
|}""".stripMargin)
}
}