blob: 82e91dc6833bcca9adbffc65d8c813477bba6dd3 [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.nio.charset.StandardCharsets
import java.util
import java.util.concurrent.TimeUnit
import com.google.common.base.Strings
import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
import io.restassured.RestAssured
import io.restassured.RestAssured.`given`
import io.restassured.builder.ResponseSpecBuilder
import org.apache.http.HttpStatus.SC_OK
import org.apache.james.GuiceJamesServer
import org.apache.james.core.quota.QuotaSizeLimit
import org.apache.james.jmap.http.UserCredential
import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ACCOUNT_ID, ANDRE, ANDRE_ACCOUNT_ID, ANDRE_PASSWORD, BOB, BOB_PASSWORD, DOMAIN, authScheme, baseRequestSpecBuilder}
import org.apache.james.jmap.rfc8621.contract.QuotaMailingTest.andreDraftsPath
import org.apache.james.junit.categories.BasicFeature
import org.apache.james.mailbox.DefaultMailboxes
import org.apache.james.mailbox.MessageManager.AppendCommand
import org.apache.james.mailbox.model.{MailboxConstants, MailboxPath, MessageId}
import org.apache.james.mime4j.dom.Message
import org.apache.james.modules.{MailboxProbeImpl, QuotaProbesImpl}
import org.apache.james.utils.DataProbeImpl
import org.awaitility.Awaitility
import org.awaitility.Durations.ONE_HUNDRED_MILLISECONDS
import org.hamcrest.Matchers.{containsString, hasItem}
import org.junit.experimental.categories.Category
import org.junit.jupiter.api.{BeforeEach, Test}
import scala.jdk.CollectionConverters._
object QuotaMailingTest {
private val andreDraftsPath = MailboxPath.forUser(ANDRE, DefaultMailboxes.DRAFTS)
}
trait QuotaMailingTest {
private lazy val slowPacedPollInterval = ONE_HUNDRED_MILLISECONDS
private lazy val calmlyAwait = Awaitility.`with`
.pollInterval(slowPacedPollInterval)
.and.`with`.pollDelay(slowPacedPollInterval)
.await
private lazy val awaitAtMostTwoMinutes = calmlyAwait.atMost(2, TimeUnit.MINUTES)
@BeforeEach
def setUp(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[DataProbeImpl])
.fluent
.addDomain(DOMAIN.asString)
.addUser(BOB.asString, BOB_PASSWORD)
.addUser(ANDRE.asString, ANDRE_PASSWORD)
val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, BOB.asString, DefaultMailboxes.INBOX)
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, ANDRE.asString, DefaultMailboxes.INBOX)
mailboxProbe.createMailbox(andreDraftsPath)
RestAssured.requestSpecification = baseRequestSpecBuilder(server)
.setAuth(authScheme(UserCredential(BOB, BOB_PASSWORD)))
.build
}
@Category(Array(classOf[BasicFeature]))
@Test
def shouldSendANoticeWhenThresholdExceeded(server: GuiceJamesServer): Unit = {
val quotaProbe = server.getProbe(classOf[QuotaProbesImpl])
quotaProbe.setMaxStorage(quotaProbe.getQuotaRoot(MailboxPath.inbox(BOB)), QuotaSizeLimit.size(100 * 1000))
andreSendMailToBob(server)
// Bob receives a mail big enough to trigger a configured threshold
awaitAtMostTwoMinutes.until(() => listMessageIds().size == 2)
val ids: List[String] = listMessageIds().asScala.toList
val idString: String = concatMessageIds(ids)
val request =
s"""{
| "using": ["urn:ietf:params:jmap:core","urn:ietf:params:jmap:mail", "urn:ietf:params:jmap:submission"],
| "methodCalls": [[
| "Email/get",
| {
| "accountId": "$ACCOUNT_ID",
| "ids": [$idString]
| },
| "c1"]]
|}""".stripMargin
`given`
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
.body(request)
.when
.post()
.`then`
.statusCode(SC_OK)
.log.ifValidationFails()
.body("methodResponses[0][1].list.subject",
hasItem("Warning: Your email usage just exceeded a configured threshold"))
}
@Test
def configurationShouldBeWellLoaded(server: GuiceJamesServer): Unit = {
val quotaProbe = server.getProbe(classOf[QuotaProbesImpl])
quotaProbe.setMaxStorage(quotaProbe.getQuotaRoot(MailboxPath.inbox(BOB)), QuotaSizeLimit.size(100 * 1000))
andreSendMailToBob(server)
// Bob receives a mail big enough to trigger a 10% configured threshold
awaitAtMostTwoMinutes.until(() => listMessageIds().size == 2)
andreSendMailToBob(server)
// Bob receives a mail big enough to trigger a 20% configured threshold
awaitAtMostTwoMinutes.until(() => listMessageIds().size == 4)
val ids: List[String] = listMessageIds().asScala.toList
val idString: String = concatMessageIds(ids)
val request =
s"""{
| "using": ["urn:ietf:params:jmap:core","urn:ietf:params:jmap:mail", "urn:ietf:params:jmap:submission"],
| "methodCalls": [[
| "Email/get",
| {
| "accountId": "$ACCOUNT_ID",
| "ids": [$idString]
| },
| "c1"]]
|}""".stripMargin
`given`
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
.body(request)
.when
.post()
.`then`
.statusCode(SC_OK)
.log.ifValidationFails()
.body("methodResponses[0][1].list.preview",
hasItem(containsString("You currently occupy more than 10 % of the total size allocated to you")))
.body("methodResponses[0][1].list.preview",
hasItem(containsString("You currently occupy more than 20 % of the total size allocated to you")))
}
private def andreSendMailToBob(server: GuiceJamesServer): Unit = {
val message: Message = Message.Builder
.of
.setSubject("test")
.setSender(ANDRE.asString)
.setFrom("ANDRE <" + ANDRE.asString + ">")
.setTo(BOB.asString)
.setBody(Strings.repeat("123456789\n", 12 * 100), StandardCharsets.UTF_8)
.build
val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(ANDRE.asString(), andreDraftsPath, AppendCommand.builder().build(message))
.getMessageId
val requestAndre =
s"""{
| "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail", "urn:ietf:params:jmap:submission"],
| "methodCalls": [
| ["EmailSubmission/set", {
| "accountId": "$ANDRE_ACCOUNT_ID",
| "create": {
| "k1490": {
| "emailId": "${messageId.serialize}",
| "envelope": {
| "mailFrom": {"email": "${ANDRE.asString}"},
| "rcptTo": [{"email": "${BOB.asString}"}]
| }
| }
| }
| }, "c1"]]
|}""".stripMargin
`given`(
baseRequestSpecBuilder(server)
.setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
.addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
.setBody(requestAndre)
.build, new ResponseSpecBuilder().build)
.post
.`then`
.statusCode(SC_OK)
}
private def listMessageIds(): util.ArrayList[String] = {
val request =
s"""{
| "using": ["urn:ietf:params:jmap:core","urn:ietf:params:jmap:mail", "urn:ietf:params:jmap:submission"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "$ACCOUNT_ID"
| },
| "c1"]]
|}""".stripMargin
`given`
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
.body(request)
.when
.post()
.`then`
.statusCode(SC_OK)
.extract
.body
.path("methodResponses[0][1].ids")
}
private def concatMessageIds(ids: List[String]): String =
ids.map(id => "\"" + id + "\"")
.mkString(",")
}