This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git


The following commit(s) were added to refs/heads/master by this push:
     new 0bfb7d760a JAMES-3830 Implement JMAP Quota/get (#1242)
0bfb7d760a is described below

commit 0bfb7d760a67dd07353824e2c94d935744c14739
Author: vttran <vtt...@linagora.com>
AuthorDate: Sat Oct 15 13:42:07 2022 +0700

    JAMES-3830 Implement JMAP Quota/get (#1242)
---
 .../org/apache/james/mailbox/probe/QuotaProbe.java |   5 +
 .../org/apache/james/modules/QuotaProbesImpl.java  |  11 +
 .../james/jmap/rfc8621/RFC8621MethodsModule.java   |   2 +
 .../distributed/DistributedQuotaGetMethodTest.java |  55 ++
 .../rfc8621/contract/QuotaGetMethodContract.scala  | 906 +++++++++++++++++++++
 .../rfc8621/memory/MemoryQuotaGetMethodTest.java   |  44 +
 .../apache/james/jmap/json/QuotaSerializer.scala   |  73 ++
 .../scala/org/apache/james/jmap/mail/Quotas.scala  | 136 +++-
 .../apache/james/jmap/method/QuotaGetMethod.scala  | 131 +++
 .../james/jmap/json/QuotaSerializerTest.scala      | 222 +++++
 10 files changed, 1582 insertions(+), 3 deletions(-)

diff --git 
a/mailbox/api/src/main/java/org/apache/james/mailbox/probe/QuotaProbe.java 
b/mailbox/api/src/main/java/org/apache/james/mailbox/probe/QuotaProbe.java
index b3fa677b90..45479bfa02 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/probe/QuotaProbe.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/probe/QuotaProbe.java
@@ -21,6 +21,7 @@ package org.apache.james.mailbox.probe;
 
 import java.util.Optional;
 
+import org.apache.james.core.Domain;
 import org.apache.james.core.quota.QuotaCountLimit;
 import org.apache.james.core.quota.QuotaCountUsage;
 import org.apache.james.core.quota.QuotaSizeLimit;
@@ -54,4 +55,8 @@ public interface QuotaProbe {
 
     void setGlobalMaxStorage(QuotaSizeLimit maxGlobalSize) throws 
MailboxException;
 
+    void setDomainMaxMessage(Domain domain, QuotaCountLimit count) throws 
MailboxException;
+
+    void setDomainMaxStorage(Domain domain, QuotaSizeLimit size) throws 
MailboxException;
+
 }
\ No newline at end of file
diff --git 
a/server/container/guice/mailbox/src/main/java/org/apache/james/modules/QuotaProbesImpl.java
 
b/server/container/guice/mailbox/src/main/java/org/apache/james/modules/QuotaProbesImpl.java
index 58fa279e43..0d97356051 100644
--- 
a/server/container/guice/mailbox/src/main/java/org/apache/james/modules/QuotaProbesImpl.java
+++ 
b/server/container/guice/mailbox/src/main/java/org/apache/james/modules/QuotaProbesImpl.java
@@ -23,6 +23,7 @@ import java.util.Optional;
 
 import javax.inject.Inject;
 
+import org.apache.james.core.Domain;
 import org.apache.james.core.quota.QuotaCountLimit;
 import org.apache.james.core.quota.QuotaCountUsage;
 import org.apache.james.core.quota.QuotaSizeLimit;
@@ -104,4 +105,14 @@ public class QuotaProbesImpl implements QuotaProbe, 
GuiceProbe {
     public void setGlobalMaxStorage(QuotaSizeLimit maxGlobalSize) throws 
MailboxException {
         maxQuotaManager.setGlobalMaxStorage(maxGlobalSize);
     }
+
+    @Override
+    public void setDomainMaxMessage(Domain domain, QuotaCountLimit count) 
throws MailboxException {
+        maxQuotaManager.setDomainMaxMessage(domain, count);
+    }
+
+    @Override
+    public void setDomainMaxStorage(Domain domain, QuotaSizeLimit size) throws 
MailboxException {
+        maxQuotaManager.setDomainMaxStorage(domain, size);
+    }
 }
diff --git 
a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java
 
b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java
index 5afc4ad357..fbfe7c036e 100644
--- 
a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java
+++ 
b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java
@@ -64,6 +64,7 @@ import org.apache.james.jmap.method.MailboxSetMethod;
 import org.apache.james.jmap.method.Method;
 import org.apache.james.jmap.method.PushSubscriptionGetMethod;
 import org.apache.james.jmap.method.PushSubscriptionSetMethod;
+import org.apache.james.jmap.method.QuotaGetMethod;
 import org.apache.james.jmap.method.SystemZoneIdProvider;
 import org.apache.james.jmap.method.ThreadChangesMethod;
 import org.apache.james.jmap.method.ThreadGetMethod;
@@ -142,6 +143,7 @@ public class RFC8621MethodsModule extends AbstractModule {
         methods.addBinding().to(MDNSendMethod.class);
         methods.addBinding().to(PushSubscriptionGetMethod.class);
         methods.addBinding().to(PushSubscriptionSetMethod.class);
+        methods.addBinding().to(QuotaGetMethod.class);
         methods.addBinding().to(ThreadChangesMethod.class);
         methods.addBinding().to(ThreadGetMethod.class);
         methods.addBinding().to(VacationResponseGetMethod.class);
diff --git 
a/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedQuotaGetMethodTest.java
 
b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedQuotaGetMethodTest.java
new file mode 100644
index 0000000000..eb6612b099
--- /dev/null
+++ 
b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedQuotaGetMethodTest.java
@@ -0,0 +1,55 @@
+/****************************************************************
+ * 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.distributed;
+
+import org.apache.james.CassandraExtension;
+import org.apache.james.CassandraRabbitMQJamesConfiguration;
+import org.apache.james.CassandraRabbitMQJamesServerMain;
+import org.apache.james.DockerOpenSearchExtension;
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.jmap.rfc8621.contract.QuotaGetMethodContract;
+import org.apache.james.modules.AwsS3BlobStoreExtension;
+import org.apache.james.modules.RabbitMQExtension;
+import org.apache.james.modules.TestJMAPServerModule;
+import org.apache.james.modules.blobstore.BlobStoreConfiguration;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class DistributedQuotaGetMethodTest implements QuotaGetMethodContract {
+
+    @RegisterExtension
+    static JamesServerExtension testExtension = new 
JamesServerBuilder<CassandraRabbitMQJamesConfiguration>(tmpDir ->
+        CassandraRabbitMQJamesConfiguration.builder()
+            .workingDirectory(tmpDir)
+            .configurationFromClasspath()
+            .blobStore(BlobStoreConfiguration.builder()
+                .s3()
+                .disableCache()
+                .deduplication()
+                .noCryptoConfig())
+            .build())
+        .extension(new DockerOpenSearchExtension())
+        .extension(new CassandraExtension())
+        .extension(new RabbitMQExtension())
+        .extension(new AwsS3BlobStoreExtension())
+        .server(configuration -> 
CassandraRabbitMQJamesServerMain.createServer(configuration)
+            .overrideWith(new TestJMAPServerModule()))
+        .build();
+}
diff --git 
a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/QuotaGetMethodContract.scala
 
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/QuotaGetMethodContract.scala
new file mode 100644
index 0000000000..ed3f742667
--- /dev/null
+++ 
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/QuotaGetMethodContract.scala
@@ -0,0 +1,906 @@
+/****************************************************************
+ * 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 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.core.quota.{QuotaCountLimit, QuotaSizeLimit}
+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.mail.{CountResourceType, QuotaIdFactory}
+import 
org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, 
ANDRE, ANDRE_PASSWORD, BOB, BOB_PASSWORD, DOMAIN, authScheme, 
baseRequestSpecBuilder}
+import org.apache.james.mailbox.MessageManager.AppendCommand
+import org.apache.james.mailbox.model.MailboxPath
+import org.apache.james.mime4j.dom.Message
+import org.apache.james.modules.{MailboxProbeImpl, QuotaProbesImpl}
+import org.apache.james.utils.DataProbeImpl
+import org.junit.jupiter.api.{BeforeEach, Test}
+
+import java.nio.charset.StandardCharsets
+
+
+trait QuotaGetMethodContract {
+
+  @BeforeEach
+  def setUp(server: GuiceJamesServer): Unit = {
+    server.getProbe(classOf[DataProbeImpl])
+      .fluent
+      .addDomain(DOMAIN.asString)
+      .addUser(BOB.asString, BOB_PASSWORD)
+      .addUser(ANDRE.asString(), ANDRE_PASSWORD)
+
+    requestSpecification = baseRequestSpecBuilder(server)
+      .setAuth(authScheme(UserCredential(BOB, BOB_PASSWORD)))
+      .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .build
+  }
+
+  @Test
+  def listShouldEmptyWhenAccountDoesNotHaveQuotas(): Unit = {
+    val response = `given`
+      .body(
+        s"""{
+           |  "using": [
+           |    "urn:ietf:params:jmap:core",
+           |    "urn:ietf:params:jmap:quota"],
+           |  "methodCalls": [[
+           |    "Quota/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": [[
+         |    "Quota/get",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "state": "${INSTANCE.value}",
+         |      "list": [],
+         |      "notFound": []
+         |    },
+         |    "c1"]]
+         |}""".stripMargin)
+  }
+
+  @Test
+  def quotaGetShouldReturnListWhenQuotasIsProvided(server: GuiceJamesServer): 
Unit = {
+    val quotaProbe = server.getProbe(classOf[QuotaProbesImpl])
+    val bobQuotaRoot = quotaProbe.getQuotaRoot(MailboxPath.inbox(BOB))
+    quotaProbe.setMaxMessageCount(bobQuotaRoot, QuotaCountLimit.count(100L))
+    quotaProbe.setMaxStorage(bobQuotaRoot, QuotaSizeLimit.size(99L))
+
+    val response = `given`
+      .body(
+        s"""{
+           |  "using": [
+           |    "urn:ietf:params:jmap:core",
+           |    "urn:ietf:params:jmap:quota"],
+           |  "methodCalls": [[
+           |    "Quota/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": [
+         |        [
+         |            "Quota/get",
+         |            {
+         |                "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |                "notFound": [],
+         |                "state": "${INSTANCE.value}",
+         |                "list": [
+         |                    {
+         |                        "used": 0,
+         |                        "name": "account:count:Mail",
+         |                        "id": 
"08417be420b6dd6fa77d48fb2438e0d19108cd29424844bb109b52d356fab528",
+         |                        "dataTypes": [
+         |                            "Mail"
+         |                        ],
+         |                        "limit": 100,
+         |                        "warnLimit": 90,
+         |                        "resourceType": "count",
+         |                        "scope": "account"
+         |                    },
+         |                    {
+         |                        "used": 0,
+         |                        "name": "account:octets:Mail",
+         |                        "id": 
"eab6ce8ac5d9730a959e614854410cf39df98ff3760a623b8e540f36f5184947",
+         |                        "dataTypes": [
+         |                            "Mail"
+         |                        ],
+         |                        "limit": 99,
+         |                        "warnLimit": 89,
+         |                        "resourceType": "octets",
+         |                        "scope": "account"
+         |                    }
+         |                ]
+         |            },
+         |            "c1"
+         |        ]
+         |    ]
+         |}
+         |""".stripMargin)
+  }
+
+  @Test
+  def quotaGetShouldReturnEmptyListWhenIdsAreEmpty(server: GuiceJamesServer): 
Unit = {
+    val quotaProbe = server.getProbe(classOf[QuotaProbesImpl])
+    val bobQuotaRoot = quotaProbe.getQuotaRoot(MailboxPath.inbox(BOB))
+    quotaProbe.setMaxMessageCount(bobQuotaRoot, QuotaCountLimit.count(100L))
+
+    val response = `given`
+      .body(
+        s"""{
+           |  "using": [
+           |    "urn:ietf:params:jmap:core",
+           |    "urn:ietf:params:jmap:quota"],
+           |  "methodCalls": [[
+           |    "Quota/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": [
+         |        [
+         |            "Quota/get",
+         |            {
+         |                "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |                "notFound": [],
+         |                "state": "${INSTANCE.value}",
+         |                "list": []
+         |            },
+         |            "c1"
+         |        ]
+         |    ]
+         |}
+         |""".stripMargin)
+  }
+
+  @Test
+  def quotaGetShouldReturnNotFoundWhenIdDoesNotExist(server: 
GuiceJamesServer): Unit = {
+    val quotaProbe = server.getProbe(classOf[QuotaProbesImpl])
+    val bobQuotaRoot = quotaProbe.getQuotaRoot(MailboxPath.inbox(BOB))
+    quotaProbe.setMaxMessageCount(bobQuotaRoot, QuotaCountLimit.count(100L))
+
+    val response = `given`
+      .body(
+        s"""{
+           |  "using": [
+           |    "urn:ietf:params:jmap:core",
+           |    "urn:ietf:params:jmap:quota"],
+           |  "methodCalls": [[
+           |    "Quota/get",
+           |    {
+           |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |      "ids": ["notfound123"]
+           |    },
+           |    "c1"]]
+           |}""".stripMargin)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response).isEqualTo(
+      s"""{
+         |    "sessionState": "${SESSION_STATE.value}",
+         |    "methodResponses": [
+         |        [
+         |            "Quota/get",
+         |            {
+         |                "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |                "notFound": [ "notfound123" ],
+         |                "state": "${INSTANCE.value}",
+         |                "list": []
+         |            },
+         |            "c1"
+         |        ]
+         |    ]
+         |}
+         |""".stripMargin)
+  }
+
+  @Test
+  def quotaGetShouldReturnNotFoundAndListWhenMixCases(server: 
GuiceJamesServer): Unit = {
+    val quotaProbe = server.getProbe(classOf[QuotaProbesImpl])
+    val bobQuotaRoot = quotaProbe.getQuotaRoot(MailboxPath.inbox(BOB))
+    quotaProbe.setMaxMessageCount(bobQuotaRoot, QuotaCountLimit.count(100L))
+    quotaProbe.setMaxStorage(bobQuotaRoot, QuotaSizeLimit.size(900))
+
+    val quotaId = QuotaIdFactory.from(bobQuotaRoot, CountResourceType)
+
+    val response = `given`
+      .body(
+        s"""{
+           |  "using": [
+           |    "urn:ietf:params:jmap:core",
+           |    "urn:ietf:params:jmap:quota"],
+           |  "methodCalls": [[
+           |    "Quota/get",
+           |    {
+           |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |      "ids": ["notfound123", "${quotaId.value}"]
+           |    },
+           |    "c1"]]
+           |}""".stripMargin)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response).isEqualTo(
+      s"""{
+         |    "sessionState": "${SESSION_STATE.value}",
+         |    "methodResponses": [
+         |        [
+         |            "Quota/get",
+         |            {
+         |                "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |                "notFound": [ "notfound123" ],
+         |                "state": "${INSTANCE.value}",
+         |                "list": [
+         |                    {
+         |                        "used": 0,
+         |                        "name": "account:count:Mail",
+         |                        "id": 
"08417be420b6dd6fa77d48fb2438e0d19108cd29424844bb109b52d356fab528",
+         |                        "dataTypes": [
+         |                            "Mail"
+         |                        ],
+         |                        "limit": 100,
+         |                        "warnLimit": 90,
+         |                        "resourceType": "count",
+         |                        "scope": "account"
+         |                    }
+         |                ]
+         |            },
+         |            "c1"
+         |        ]
+         |    ]
+         |}
+         |""".stripMargin)
+  }
+
+  @Test
+  def quotaGetShouldReturnRightUsageQuota(server: GuiceJamesServer): Unit = {
+    val quotaProbe = server.getProbe(classOf[QuotaProbesImpl])
+    val bobQuotaRoot = quotaProbe.getQuotaRoot(MailboxPath.inbox(BOB))
+    quotaProbe.setMaxMessageCount(bobQuotaRoot, QuotaCountLimit.count(100L))
+    quotaProbe.setMaxStorage(bobQuotaRoot, QuotaSizeLimit.size(900L))
+
+    
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+    server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString(), MailboxPath.inbox(BOB), 
AppendCommand.from(Message.Builder
+        .of
+        .setSubject("test")
+        .setBody("testmail", StandardCharsets.UTF_8)
+        .build))
+      .getMessageId.serialize()
+
+    val response = `given`
+      .body(
+        s"""{
+           |  "using": [
+           |    "urn:ietf:params:jmap:core",
+           |    "urn:ietf:params:jmap:quota"],
+           |  "methodCalls": [[
+           |    "Quota/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": [
+         |        [
+         |            "Quota/get",
+         |            {
+         |                "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |                "notFound": [ ],
+         |                "state": "${INSTANCE.value}",
+         |                "list": [
+         |                    {
+         |                        "used": 1,
+         |                        "name": "account:count:Mail",
+         |                        "id": 
"08417be420b6dd6fa77d48fb2438e0d19108cd29424844bb109b52d356fab528",
+         |                        "dataTypes": [
+         |                            "Mail"
+         |                        ],
+         |                        "limit": 100,
+         |                        "warnLimit": 90,
+         |                        "resourceType": "count",
+         |                        "scope": "account"
+         |                    },
+         |                    {
+         |                        "used": 85,
+         |                        "name": "account:octets:Mail",
+         |                        "id": 
"eab6ce8ac5d9730a959e614854410cf39df98ff3760a623b8e540f36f5184947",
+         |                        "dataTypes": [
+         |                            "Mail"
+         |                        ],
+         |                        "limit": 900,
+         |                        "warnLimit": 810,
+         |                        "resourceType": "octets",
+         |                        "scope": "account"
+         |                    }
+         |                ]
+         |            },
+         |            "c1"
+         |        ]
+         |    ]
+         |}
+         |""".stripMargin)
+  }
+
+
+  @Test
+  def quotaGetShouldFailWhenWrongAccountId(): Unit = {
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:quota"],
+         |  "methodCalls": [[
+         |    "Quota/get",
+         |    {
+         |      "accountId": "unknownAccountId",
+         |      "ids": null
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    val response = `given`
+      .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
+  def quotaGetShouldFailWhenOmittingOneCapability(): Unit = {
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core"],
+         |  "methodCalls": [[
+         |    "Quota/get",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "ids": null
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    val response = `given`
+      .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": "unknownMethod",
+         |      "description":"Missing capability(ies): 
urn:ietf:params:jmap:quota"
+         |    },
+         |    "c1"]]
+         |}""".stripMargin)
+  }
+
+  @Test
+  def quotaGetShouldFailWhenOmittingAllCapability(): Unit = {
+    val request =
+      s"""{
+         |  "using": [],
+         |  "methodCalls": [[
+         |    "Quota/get",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "ids": null
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    val response = `given`
+      .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": "unknownMethod",
+         |      "description":"Missing capability(ies): 
urn:ietf:params:jmap:quota, urn:ietf:params:jmap:core"
+         |    },
+         |    "c1"]]
+         |}""".stripMargin)
+  }
+
+  @Test
+  def quotaGetShouldNotReturnQuotaDataOfOtherAccount(server: 
GuiceJamesServer): Unit = {
+    val quotaProbe = server.getProbe(classOf[QuotaProbesImpl])
+    val andreQuotaRoot = quotaProbe.getQuotaRoot(MailboxPath.inbox(ANDRE))
+    quotaProbe.setMaxMessageCount(andreQuotaRoot, QuotaCountLimit.count(100L))
+
+    val response = `given`
+      .body(
+        s"""{
+           |  "using": [
+           |    "urn:ietf:params:jmap:core",
+           |    "urn:ietf:params:jmap:quota"],
+           |  "methodCalls": [[
+           |    "Quota/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": [[
+         |    "Quota/get",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "state": "${INSTANCE.value}",
+         |      "list": [],
+         |      "notFound": []
+         |    },
+         |    "c1"]]
+         |}""".stripMargin)
+  }
+
+  @Test
+  def quotaGetShouldReturnNotFoundWhenDoesNotPermission(server: 
GuiceJamesServer): Unit = {
+    val quotaProbe = server.getProbe(classOf[QuotaProbesImpl])
+    val andreQuotaRoot = quotaProbe.getQuotaRoot(MailboxPath.inbox(ANDRE))
+    quotaProbe.setMaxMessageCount(andreQuotaRoot, QuotaCountLimit.count(100L))
+
+    val quotaId = QuotaIdFactory.from(andreQuotaRoot, CountResourceType)
+
+    val response = `given`
+      .body(
+        s"""{
+           |  "using": [
+           |    "urn:ietf:params:jmap:core",
+           |    "urn:ietf:params:jmap:quota"],
+           |  "methodCalls": [[
+           |    "Quota/get",
+           |    {
+           |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |      "ids": ["${quotaId.value}"]
+           |    },
+           |    "c1"]]
+           |}""".stripMargin)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response).isEqualTo(
+      s"""{
+         |  "sessionState": "${SESSION_STATE.value}",
+         |  "methodResponses": [[
+         |    "Quota/get",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "state": "${INSTANCE.value}",
+         |      "list": [],
+         |      "notFound": [ "${quotaId}" ]
+         |    },
+         |    "c1"]]
+         |}""".stripMargin)
+  }
+
+  @Test
+  def quotaGetShouldReturnIdWhenNoPropertiesRequested(server: 
GuiceJamesServer): Unit = {
+    val quotaProbe = server.getProbe(classOf[QuotaProbesImpl])
+    val quotaRoot = quotaProbe.getQuotaRoot(MailboxPath.inbox(BOB))
+    quotaProbe.setMaxMessageCount(quotaRoot, QuotaCountLimit.count(100L))
+
+    val response = `given`
+      .body(
+        s"""{
+           |  "using": [
+           |    "urn:ietf:params:jmap:core",
+           |    "urn:ietf:params:jmap:quota"],
+           |  "methodCalls": [[
+           |    "Quota/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": [[
+         |    "Quota/get",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "state": "${INSTANCE.value}",
+         |      "list": [
+         |        {
+         |          "id": 
"08417be420b6dd6fa77d48fb2438e0d19108cd29424844bb109b52d356fab528"
+         |        }
+         |      ],
+         |      "notFound": []
+         |    },
+         |    "c1"]]
+         |}""".stripMargin)
+  }
+
+  @Test
+  def quotaGetShouldReturnOnlyRequestedProperties(server: GuiceJamesServer): 
Unit = {
+    val quotaProbe = server.getProbe(classOf[QuotaProbesImpl])
+    val quotaRoot = quotaProbe.getQuotaRoot(MailboxPath.inbox(BOB))
+    quotaProbe.setMaxMessageCount(quotaRoot, QuotaCountLimit.count(100L))
+
+    val response = `given`
+      .body(
+        s"""{
+           |  "using": [
+           |    "urn:ietf:params:jmap:core",
+           |    "urn:ietf:params:jmap:quota"],
+           |  "methodCalls": [[
+           |    "Quota/get",
+           |    {
+           |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |      "ids": null,
+           |      "properties": ["name","used","limit"]
+           |    },
+           |    "c1"]]
+           |}""".stripMargin)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response).isEqualTo(
+      s"""{
+         |  "sessionState": "${SESSION_STATE.value}",
+         |  "methodResponses": [[
+         |    "Quota/get",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "state": "${INSTANCE.value}",
+         |      "list": [
+         |        {
+         |          "id": 
"08417be420b6dd6fa77d48fb2438e0d19108cd29424844bb109b52d356fab528",
+         |          "used": 0,
+         |          "name": "account:count:Mail",
+         |          "limit": 100
+         |        }
+         |      ],
+         |      "notFound": []
+         |    },
+         |    "c1"]]
+         |}""".stripMargin)
+  }
+
+  @Test
+  def quotaGetShouldFailWhenInvalidProperties(server: GuiceJamesServer): Unit 
= {
+    val quotaProbe = server.getProbe(classOf[QuotaProbesImpl])
+    val quotaRoot = quotaProbe.getQuotaRoot(MailboxPath.inbox(BOB))
+    quotaProbe.setMaxMessageCount(quotaRoot, QuotaCountLimit.count(100L))
+
+    val response = `given`
+      .body(
+        s"""{
+           |  "using": [
+           |    "urn:ietf:params:jmap:core",
+           |    "urn:ietf:params:jmap:quota"],
+           |  "methodCalls": [[
+           |    "Quota/get",
+           |    {
+           |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |      "ids": null,
+           |      "properties": ["invalid"]
+           |    },
+           |    "c1"]]
+           |}""".stripMargin)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response).isEqualTo(
+      s"""{
+         |    "sessionState": "${SESSION_STATE.value}",
+         |    "methodResponses": [
+         |        [
+         |            "error",
+         |            {
+         |                "type": "invalidArguments",
+         |                "description": "The following properties [invalid] 
do not exist."
+         |            },
+         |            "c1"
+         |        ]
+         |    ]
+         |}""".stripMargin)
+  }
+
+  @Test
+  def quotaGetShouldFailWhenInvalidIds(server: GuiceJamesServer): Unit = {
+    val quotaProbe = server.getProbe(classOf[QuotaProbesImpl])
+    val quotaRoot = quotaProbe.getQuotaRoot(MailboxPath.inbox(BOB))
+    quotaProbe.setMaxMessageCount(quotaRoot, QuotaCountLimit.count(100L))
+
+    val response = `given`
+      .body(
+        s"""{
+           |  "using": [
+           |    "urn:ietf:params:jmap:core",
+           |    "urn:ietf:params:jmap:quota"],
+           |  "methodCalls": [[
+           |    "Quota/get",
+           |    {
+           |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |      "ids": ["#==id"]
+           |    },
+           |    "c1"]]
+           |}""".stripMargin)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response).isEqualTo(
+      s"""{
+         |    "sessionState": "${SESSION_STATE.value}",
+         |    "methodResponses": [
+         |        [
+         |            "error",
+         |            {
+         |                "type": "invalidArguments",
+         |                "description": "$${json-unit.any-string}"
+         |            },
+         |            "c1"
+         |        ]
+         |    ]
+         |}""".stripMargin)
+  }
+
+  @Test
+  def quotaGetShouldReturnOnlyUserQuota(server: GuiceJamesServer): Unit = {
+    val quotaProbe = server.getProbe(classOf[QuotaProbesImpl])
+    val bobQuotaRoot = quotaProbe.getQuotaRoot(MailboxPath.inbox(BOB))
+    quotaProbe.setMaxMessageCount(bobQuotaRoot, QuotaCountLimit.count(100L))
+    quotaProbe.setMaxStorage(bobQuotaRoot, QuotaSizeLimit.size(101L))
+
+    quotaProbe.setGlobalMaxMessageCount(QuotaCountLimit.count(90L))
+    quotaProbe.setGlobalMaxStorage(QuotaSizeLimit.size(99L))
+
+    quotaProbe.setDomainMaxMessage(DOMAIN, QuotaCountLimit.count(80L))
+    quotaProbe.setDomainMaxStorage(DOMAIN, QuotaSizeLimit.size(88L))
+
+    
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+    server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString(), MailboxPath.inbox(BOB), 
AppendCommand.from(Message.Builder
+        .of
+        .setSubject("test")
+        .setBody("testmail", StandardCharsets.UTF_8)
+        .build))
+      .getMessageId.serialize()
+
+    val response = `given`
+      .body(
+        s"""{
+           |  "using": [
+           |    "urn:ietf:params:jmap:core",
+           |    "urn:ietf:params:jmap:quota"],
+           |  "methodCalls": [[
+           |    "Quota/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": [
+         |        [
+         |            "Quota/get",
+         |            {
+         |                "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |                "notFound": [],
+         |                "state": "${INSTANCE.value}",
+         |                "list": [
+         |                    {
+         |                        "used": 1,
+         |                        "name": "account:count:Mail",
+         |                        "id": 
"08417be420b6dd6fa77d48fb2438e0d19108cd29424844bb109b52d356fab528",
+         |                        "dataTypes": [
+         |                            "Mail"
+         |                        ],
+         |                        "limit": 100,
+         |                        "warnLimit": 90,
+         |                        "resourceType": "count",
+         |                        "scope": "account"
+         |                    },
+         |                    {
+         |                        "used": 85,
+         |                        "name": "account:octets:Mail",
+         |                        "id": 
"eab6ce8ac5d9730a959e614854410cf39df98ff3760a623b8e540f36f5184947",
+         |                        "dataTypes": [
+         |                            "Mail"
+         |                        ],
+         |                        "limit": 101,
+         |                        "warnLimit": 90,
+         |                        "resourceType": "octets",
+         |                        "scope": "account"
+         |                    }
+         |                ]
+         |            },
+         |            "c1"
+         |        ]
+         |    ]
+         |}
+         |""".stripMargin)
+  }
+}
diff --git 
a/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryQuotaGetMethodTest.java
 
b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryQuotaGetMethodTest.java
new file mode 100644
index 0000000000..e2bfac204c
--- /dev/null
+++ 
b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryQuotaGetMethodTest.java
@@ -0,0 +1,44 @@
+/****************************************************************
+ * 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.memory;
+
+import static 
org.apache.james.data.UsersRepositoryModuleChooser.Implementation.DEFAULT;
+
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.MemoryJamesConfiguration;
+import org.apache.james.MemoryJamesServerMain;
+import org.apache.james.jmap.rfc8621.contract.QuotaGetMethodContract;
+import org.apache.james.modules.TestJMAPServerModule;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class MemoryQuotaGetMethodTest implements QuotaGetMethodContract {
+
+    @RegisterExtension
+    static JamesServerExtension testExtension = new 
JamesServerBuilder<MemoryJamesConfiguration>(tmpDir ->
+        MemoryJamesConfiguration.builder()
+            .workingDirectory(tmpDir)
+            .configurationFromClasspath()
+            .usersRepository(DEFAULT)
+            .build())
+        .server(configuration -> 
MemoryJamesServerMain.createServer(configuration)
+            .overrideWith(new TestJMAPServerModule()))
+        .build();
+}
diff --git 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/QuotaSerializer.scala
 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/QuotaSerializer.scala
new file mode 100644
index 0000000000..2ca89be9ab
--- /dev/null
+++ 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/QuotaSerializer.scala
@@ -0,0 +1,73 @@
+/****************************************************************
+ * 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.json
+
+import eu.timepit.refined
+import org.apache.james.jmap.core.Id.IdConstraint
+import org.apache.james.jmap.core.{Properties, UuidState}
+import org.apache.james.jmap.mail.{DataType, JmapQuota, QuotaDescription, 
QuotaGetRequest, QuotaGetResponse, QuotaIds, QuotaName, QuotaNotFound, 
ResourceType, Scope, UnparsedQuotaId}
+import play.api.libs.json._
+
+object QuotaSerializer {
+
+  private implicit val unparsedQuotaIdWrites: Writes[UnparsedQuotaId] = 
Json.valueWrites[UnparsedQuotaId]
+  private implicit val unparsedQuotaIdReads: Reads[UnparsedQuotaId] = {
+    case JsString(string) => refined.refineV[IdConstraint](string)
+      .fold(
+        e => JsError(s"vacation response id does not match Id constraints: 
$e"),
+        id => JsSuccess(UnparsedQuotaId(id)))
+    case _ => JsError("vacation response id needs to be represented by a 
JsString")
+  }
+  private implicit val quotaIdsReads: Reads[QuotaIds] = 
Json.valueReads[QuotaIds]
+
+  private implicit val quotaGetRequestReads: Reads[QuotaGetRequest] = 
Json.reads[QuotaGetRequest]
+
+  private implicit val stateWrites: Writes[UuidState] = 
Json.valueWrites[UuidState]
+
+  private implicit val resourceTypeWrite: Writes[ResourceType] = resourceType 
=> JsString(resourceType.asString())
+  private implicit val scopeWrites: Writes[Scope] = scope => 
JsString(scope.asString())
+  private implicit val dataTypeWrites: Writes[DataType] = dataType => 
JsString(dataType.asString())
+  private implicit val quotaNameWrites: Writes[QuotaName] = 
Json.valueWrites[QuotaName]
+  private implicit val quotaDescriptionWrites: Writes[QuotaDescription] = 
Json.valueWrites[QuotaDescription]
+
+  private implicit val jmapQuotaWrites: Writes[JmapQuota] = 
Json.writes[JmapQuota]
+
+  private implicit val quotaNotFoundWrites: Writes[QuotaNotFound] =
+    notFound => JsArray(notFound.value.toList.map(id => JsString(id.id.value)))
+  private implicit val quotaGetResponseWrites: Writes[QuotaGetResponse] = 
Json.writes[QuotaGetResponse]
+
+  def deserializeQuotaGetRequest(input: String): JsResult[QuotaGetRequest] = 
Json.parse(input).validate[QuotaGetRequest]
+
+  def deserializeQuotaGetRequest(input: JsValue): JsResult[QuotaGetRequest] = 
Json.fromJson[QuotaGetRequest](input)
+
+  def serialize(quotaGetResponse: QuotaGetResponse, properties: Properties): 
JsValue =
+    Json.toJson(quotaGetResponse)
+      .transform((__ \ "list").json.update {
+        case JsArray(underlying) => JsSuccess(JsArray(underlying.map {
+          case jsonObject: JsObject =>
+            JmapQuota.propertiesFiltered(properties)
+              .filter(jsonObject)
+          case jsValue => jsValue
+        }))
+      }).get
+
+  def serialize(response: QuotaGetResponse): JsValue = Json.toJson(response)
+
+}
diff --git 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Quotas.scala
 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Quotas.scala
index 7fcb91c3ff..938a6612e1 100644
--- 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Quotas.scala
+++ 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Quotas.scala
@@ -19,13 +19,21 @@
 
 package org.apache.james.jmap.mail
 
+import com.google.common.hash.Hashing
+import eu.timepit.refined.auto._
 import org.apache.james.core.Domain
+import org.apache.james.core.quota.{QuotaCountLimit, QuotaCountUsage, 
QuotaSizeLimit, QuotaSizeUsage}
+import org.apache.james.jmap.core.Id.Id
 import org.apache.james.jmap.core.UnsignedInt.UnsignedInt
-import org.apache.james.mailbox.model.{QuotaRoot => ModelQuotaRoot}
+import org.apache.james.jmap.core.UuidState.INSTANCE
+import org.apache.james.jmap.core.{AccountId, Id, Properties, UnsignedInt, 
UuidState}
+import org.apache.james.jmap.method.WithAccountId
+import org.apache.james.mailbox.model.{Quota => ModelQuota, QuotaRoot => 
ModelQuotaRoot}
 
+import java.nio.charset.StandardCharsets
 import scala.compat.java8.OptionConverters._
 
-object QuotaRoot{
+object QuotaRoot {
   def toJmap(quotaRoot: ModelQuotaRoot) = QuotaRoot(quotaRoot.getValue, 
quotaRoot.getDomain.asScala)
 }
 
@@ -35,7 +43,9 @@ case class QuotaRoot(value: String, domain: Option[Domain]) {
 
 object Quotas {
   sealed trait Type
+
   case object Storage extends Type
+
   case object Message extends Type
 
   def from(quotas: Map[QuotaId, Quota]) = new Quotas(quotas)
@@ -59,4 +69,124 @@ case class Quota(quota: Map[Quotas.Type, Value]) extends 
AnyVal
 
 case class Value(used: UnsignedInt, max: Option[UnsignedInt])
 
-case class Quotas(quotas: Map[QuotaId, Quota]) extends AnyVal
\ No newline at end of file
+case class Quotas(quotas: Map[QuotaId, Quota]) extends AnyVal
+
+case class UnparsedQuotaId(id: Id)
+
+case class QuotaIds(value: List[UnparsedQuotaId])
+
+case class QuotaGetRequest(accountId: AccountId,
+                           ids: Option[QuotaIds],
+                           properties: Option[Properties]) extends 
WithAccountId
+
+object JmapQuota {
+  val WARN_LIMIT_PERCENTAGE = 0.9
+  val allProperties: Properties = Properties("id", "resourceType", "used", 
"limit", "scope", "name", "dataTypes", "warnLimit", "softLimit", "description")
+  val idProperty: Properties = Properties("id")
+
+  def propertiesFiltered(requestedProperties: Properties) : Properties = 
idProperty ++ requestedProperties
+
+  def extractUserMessageCountQuota(quota: ModelQuota[QuotaCountLimit, 
QuotaCountUsage], countQuotaIdPlaceHolder: Id): Option[JmapQuota] =
+    Option(quota.getLimitByScope.get(ModelQuota.Scope.User))
+      .map(limit => JmapQuota(
+        id = countQuotaIdPlaceHolder,
+        resourceType = CountResourceType,
+        used = UnsignedInt.liftOrThrow(quota.getUsed.asLong()),
+        limit = UnsignedInt.liftOrThrow(limit.asLong()),
+        scope = AccountScope,
+        name = QuotaName.from(AccountScope, CountResourceType, 
List(MailDataType)),
+        dataTypes = List(MailDataType),
+        warnLimit = Some(UnsignedInt.liftOrThrow((limit.asLong() * 
WARN_LIMIT_PERCENTAGE).toLong))))
+
+  def extractUserMessageSizeQuota(quota: ModelQuota[QuotaSizeLimit, 
QuotaSizeUsage], sizeQuotaIdPlaceHolder: Id): Option[JmapQuota] =
+    Option(quota.getLimitByScope.get(ModelQuota.Scope.User))
+      .map(limit => JmapQuota(
+        id = sizeQuotaIdPlaceHolder,
+        resourceType = OctetsResourceType,
+        used = UnsignedInt.liftOrThrow(quota.getUsed.asLong()),
+        limit = UnsignedInt.liftOrThrow(limit.asLong()),
+        scope = AccountScope,
+        name = QuotaName.from(AccountScope, OctetsResourceType, 
List(MailDataType)),
+        dataTypes = List(MailDataType),
+        warnLimit = Some(UnsignedInt.liftOrThrow((limit.asLong() * 
WARN_LIMIT_PERCENTAGE).toLong))))
+}
+
+case class JmapQuota(id: Id,
+                     resourceType: ResourceType,
+                     used: UnsignedInt,
+                     limit: UnsignedInt,
+                     scope: Scope,
+                     name: QuotaName,
+                     dataTypes: List[DataType],
+                     warnLimit: Option[UnsignedInt] = None,
+                     softLimit: Option[UnsignedInt] = None,
+                     description: Option[QuotaDescription] = None)
+
+object QuotaName {
+  def from(scope: Scope, resourceType: ResourceType, dataTypes: 
List[DataType]): QuotaName =
+    
QuotaName(s"${scope.asString()}:${resourceType.asString()}:${dataTypes.map(_.asString()).mkString("_")}")
+}
+
+case class QuotaName(string: String)
+
+case class QuotaDescription(string: String)
+
+sealed trait ResourceType {
+  def asString(): String
+}
+
+case object CountResourceType extends ResourceType {
+  override def asString(): String = "count"
+}
+
+case object OctetsResourceType extends ResourceType {
+  override def asString(): String = "octets"
+}
+
+trait Scope {
+  def asString(): String
+}
+
+case object AccountScope extends Scope {
+  override def asString(): String = "account"
+}
+
+trait DataType {
+  def asString(): String
+}
+
+case object MailDataType extends DataType {
+  override def asString(): String = "Mail"
+}
+
+case class QuotaGetResponse(accountId: AccountId,
+                            state: UuidState,
+                            list: List[JmapQuota],
+                            notFound: QuotaNotFound)
+
+case class QuotaNotFound(value: Set[UnparsedQuotaId]) {
+  def merge(other: QuotaNotFound): QuotaNotFound = QuotaNotFound(this.value ++ 
other.value)
+}
+
+object QuotaIdFactory {
+  def from(quotaRoot: ModelQuotaRoot, resourceType: ResourceType): Id =
+    Id.validate(Hashing.sha256.hashBytes((quotaRoot.asString() + 
resourceType.asString()).getBytes(StandardCharsets.UTF_8)).toString).toOption.get
+}
+
+object QuotaResponseGetResult {
+  def empty: QuotaResponseGetResult = QuotaResponseGetResult()
+
+  def merge(result1: QuotaResponseGetResult, result2: QuotaResponseGetResult): 
QuotaResponseGetResult = result1.merge(result2)
+}
+
+case class QuotaResponseGetResult(jmapQuotaSet: Set[JmapQuota] = Set(), 
notFound: QuotaNotFound = QuotaNotFound(Set())) {
+  def merge(other: QuotaResponseGetResult): QuotaResponseGetResult =
+    QuotaResponseGetResult(this.jmapQuotaSet ++ other.jmapQuotaSet,
+      this.notFound.merge(other.notFound))
+
+  def asResponse(accountId: AccountId): QuotaGetResponse =
+    QuotaGetResponse(accountId = accountId,
+      state = INSTANCE,
+      list = jmapQuotaSet.toList,
+      notFound = notFound)
+}
\ No newline at end of file
diff --git 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/QuotaGetMethod.scala
 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/QuotaGetMethod.scala
new file mode 100644
index 0000000000..73dc94ade1
--- /dev/null
+++ 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/QuotaGetMethod.scala
@@ -0,0 +1,131 @@
+/****************************************************************
+ * 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.method
+
+import eu.timepit.refined.auto._
+import org.apache.james.core.Username
+import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, 
JMAP_CORE, JMAP_QUOTA}
+import org.apache.james.jmap.core.Invocation.{Arguments, MethodCallId, 
MethodName}
+import org.apache.james.jmap.core.{ErrorCode, Invocation, 
MissingCapabilityException, Properties}
+import org.apache.james.jmap.json.{QuotaSerializer, ResponseSerializer}
+import org.apache.james.jmap.mail.{CountResourceType, JmapQuota, 
OctetsResourceType, QuotaGetRequest, QuotaIdFactory, QuotaNotFound, 
QuotaResponseGetResult}
+import org.apache.james.jmap.routes.SessionSupplier
+import org.apache.james.lifecycle.api.Startable
+import org.apache.james.mailbox.MailboxSession
+import org.apache.james.mailbox.model.QuotaRoot
+import org.apache.james.mailbox.quota.{QuotaManager, UserQuotaRootResolver}
+import org.apache.james.metrics.api.MetricFactory
+import org.reactivestreams.Publisher
+import play.api.libs.json.{JsError, JsObject, JsSuccess}
+import reactor.core.scala.publisher.{SFlux, SMono}
+import org.apache.james.jmap.core.Id.Id
+import org.apache.james.util.ReactorUtils
+
+import javax.inject.Inject
+
+class QuotaGetMethod @Inject()(val metricFactory: MetricFactory,
+                               val sessionSupplier: SessionSupplier,
+                               val quotaManager: QuotaManager,
+                               val quotaRootResolver: UserQuotaRootResolver) 
extends MethodRequiringAccountId[QuotaGetRequest] with Startable {
+
+  override val methodName: Invocation.MethodName = MethodName("Quota/get")
+  override val requiredCapabilities: Set[CapabilityIdentifier] = 
Set(JMAP_QUOTA, JMAP_CORE)
+  val jmapQuotaManagerWrapper: JmapQuotaManagerWrapper = 
JmapQuotaManagerWrapper(quotaManager, quotaRootResolver)
+
+  override def doProcess(capabilities: Set[CapabilityIdentifier], invocation: 
InvocationWithContext, mailboxSession: MailboxSession, request: 
QuotaGetRequest): Publisher[InvocationWithContext] = {
+    val requestedProperties: Properties = 
request.properties.getOrElse(JmapQuota.allProperties)
+
+    (requestedProperties -- JmapQuota.allProperties match {
+      case invalidProperties if invalidProperties.isEmpty() => 
getQuotaGetResponse(request, mailboxSession.getUser)
+        .reduce(QuotaResponseGetResult.empty)(QuotaResponseGetResult.merge)
+        .map(result => result.asResponse(accountId = request.accountId))
+        .map(response => Invocation(
+          methodName = methodName,
+          arguments = Arguments(QuotaSerializer.serialize(response, 
requestedProperties).as[JsObject]),
+          methodCallId = invocation.invocation.methodCallId))
+      case invalidProperties: Properties => SMono.just(Invocation.error(
+        errorCode = ErrorCode.InvalidArguments,
+        description = s"The following properties 
[${invalidProperties.format()}] do not exist.",
+        methodCallId = invocation.invocation.methodCallId))
+    })
+      .map(InvocationWithContext(_, invocation.processingContext))
+      .onErrorResume { case e: Exception => handleRequestValidationErrors(e, 
invocation.invocation.methodCallId)
+        .map(errorInvocation => InvocationWithContext(errorInvocation, 
invocation.processingContext))
+      }
+  }
+
+  override def getRequest(mailboxSession: MailboxSession, invocation: 
Invocation): Either[Exception, QuotaGetRequest] =
+    QuotaSerializer.deserializeQuotaGetRequest(invocation.arguments.value) 
match {
+      case JsSuccess(quotaGetRequest, _) => Right(quotaGetRequest)
+      case errors: JsError => Left(new 
IllegalArgumentException(ResponseSerializer.serialize(errors).toString))
+    }
+
+  private def getQuotaGetResponse(quotaGetRequest: QuotaGetRequest, username: 
Username): SFlux[QuotaResponseGetResult] =
+    quotaGetRequest.ids match {
+      case None =>
+        jmapQuotaManagerWrapper.list(username)
+          .collectSeq()
+          .map(listJmapQuota => QuotaResponseGetResult(jmapQuotaSet = 
listJmapQuota.toSet))
+          .flatMapMany(result => SFlux.just(result))
+      case Some(ids) => SFlux.fromIterable(ids.value)
+        .flatMap(id => jmapQuotaManagerWrapper.get(username, id.id)
+          .map(jmapQuota => QuotaResponseGetResult(jmapQuotaSet = 
Set(jmapQuota)))
+          .switchIfEmpty(SMono.just(QuotaResponseGetResult(notFound = 
QuotaNotFound(Set(id))))))
+    }
+
+  private def handleRequestValidationErrors(exception: Exception, 
methodCallId: MethodCallId): SMono[Invocation] = exception match {
+    case _: MissingCapabilityException => 
SMono.just(Invocation.error(ErrorCode.UnknownMethod, methodCallId))
+    case e: IllegalArgumentException => 
SMono.just(Invocation.error(ErrorCode.InvalidArguments, e.getMessage, 
methodCallId))
+  }
+}
+
+case class JmapQuotaManagerWrapper(private var quotaManager: QuotaManager,
+                                   private var quotaRootResolver: 
UserQuotaRootResolver) {
+  def get(username: Username, quotaId: Id): SFlux[JmapQuota] =
+    SMono.just(quotaRootResolver.forUser(username))
+      .flatMapMany(quotaRoot => getJmapQuota(quotaRoot, Some(quotaId)))
+
+  def list(username: Username): SFlux[JmapQuota] =
+    SMono.just(quotaRootResolver.forUser(username))
+      .flatMapMany(quotaRoot => getJmapQuota(quotaRoot))
+
+  private def getJmapQuota(quotaRoot: QuotaRoot, quotaId: Option[Id] = None): 
SFlux[JmapQuota] =
+    (quotaId match {
+      case None => SMono(quotaManager.getQuotasReactive(quotaRoot))
+        .flatMapMany(quotas => SMono.fromCallable(() => 
JmapQuota.extractUserMessageCountQuota(quotas.getMessageQuota, 
QuotaIdFactory.from(quotaRoot, CountResourceType)))
+          .mergeWith(SMono.fromCallable(() => 
JmapQuota.extractUserMessageSizeQuota(quotas.getStorageQuota, 
QuotaIdFactory.from(quotaRoot, OctetsResourceType)))))
+
+      case Some(quotaIdValue) =>
+        val quotaCountPublisher = SMono.fromCallable(() => 
QuotaIdFactory.from(quotaRoot, CountResourceType))
+          .filter(countQuotaId => 
countQuotaId.value.equals(quotaIdValue.value))
+          .flatMap(_ => SMono.fromCallable(() => 
quotaManager.getMessageQuota(quotaRoot))
+            .subscribeOn(ReactorUtils.BLOCKING_CALL_WRAPPER))
+          .map(quota => JmapQuota.extractUserMessageCountQuota(quota, 
quotaIdValue))
+
+        val quotaSizePublisher = SMono.fromCallable(() => 
QuotaIdFactory.from(quotaRoot, OctetsResourceType))
+          .filter(sizeQuotaId => sizeQuotaId.value.equals(quotaIdValue.value))
+          .flatMap(_ => SMono.fromCallable(() => 
quotaManager.getStorageQuota(quotaRoot))
+            .subscribeOn(ReactorUtils.BLOCKING_CALL_WRAPPER))
+          .map(quota => JmapQuota.extractUserMessageSizeQuota(quota, 
quotaIdValue))
+
+        quotaCountPublisher.mergeWith(quotaSizePublisher)
+    }).flatMap(SMono.justOrEmpty)
+
+}
\ No newline at end of file
diff --git 
a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/QuotaSerializerTest.scala
 
b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/QuotaSerializerTest.scala
new file mode 100644
index 0000000000..b1e790af23
--- /dev/null
+++ 
b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/QuotaSerializerTest.scala
@@ -0,0 +1,222 @@
+/****************************************************************
+ * 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.json
+
+import eu.timepit.refined.auto._
+import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
+import org.apache.james.jmap.core.{AccountId, Id, Properties, UnsignedInt, 
UuidState}
+import org.apache.james.jmap.json.Fixture.id
+import org.apache.james.jmap.json.QuotaSerializerTest.ACCOUNT_ID
+import org.apache.james.jmap.mail.{AccountScope, CountResourceType, JmapQuota, 
MailDataType, OctetsResourceType, QuotaDescription, QuotaGetRequest, 
QuotaGetResponse, QuotaIds, QuotaName, QuotaNotFound, UnparsedQuotaId}
+import org.scalatest.matchers.should.Matchers
+import org.scalatest.wordspec.AnyWordSpec
+import play.api.libs.json.{JsSuccess, Json}
+
+object QuotaSerializerTest {
+  private val ACCOUNT_ID: AccountId = AccountId(id)
+}
+
+class QuotaSerializerTest extends AnyWordSpec with Matchers {
+
+  "Deserialize QuotaGetRequest" should {
+    "succeed when properties are missing" in {
+      val expectedRequestObject = QuotaGetRequest(
+        accountId = ACCOUNT_ID,
+        ids = Some(QuotaIds(List(UnparsedQuotaId("singleton")))),
+        properties = None)
+
+      QuotaSerializer.deserializeQuotaGetRequest(
+        """
+          |{
+          |  "accountId": "aHR0cHM6Ly93d3cuYmFzZTY0ZW5jb2RlLm9yZy8",
+          |  "ids": ["singleton"]
+          |}
+          |""".stripMargin) should equal(JsSuccess(expectedRequestObject))
+    }
+
+    "succeed when properties are null" in {
+      val expectedRequestObject = QuotaGetRequest(
+        accountId = ACCOUNT_ID,
+        ids = Some(QuotaIds(List(UnparsedQuotaId("singleton")))),
+        properties = None)
+
+      QuotaSerializer.deserializeQuotaGetRequest(
+        """
+          |{
+          |  "accountId": "aHR0cHM6Ly93d3cuYmFzZTY0ZW5jb2RlLm9yZy8",
+          |  "ids": ["singleton"],
+          |  "properties": null
+          |}
+          |""".stripMargin) should equal(JsSuccess(expectedRequestObject))
+    }
+
+    "succeed when properties are empty" in {
+      val expectedRequestObject = QuotaGetRequest(
+        accountId = ACCOUNT_ID,
+        ids = Some(QuotaIds(List(UnparsedQuotaId("singleton")))),
+        properties = Some(Properties.empty()))
+
+      QuotaSerializer.deserializeQuotaGetRequest(
+        """
+          |{
+          |  "accountId": "aHR0cHM6Ly93d3cuYmFzZTY0ZW5jb2RlLm9yZy8",
+          |  "ids": ["singleton"],
+          |  "properties": []
+          |}
+          |""".stripMargin) should equal(JsSuccess(expectedRequestObject))
+    }
+
+    "succeed when ids is null" in {
+      val expectedRequestObject = QuotaGetRequest(
+        accountId = ACCOUNT_ID,
+        ids = None,
+        properties = None)
+
+      QuotaSerializer.deserializeQuotaGetRequest(
+        """
+          |{
+          |  "accountId": "aHR0cHM6Ly93d3cuYmFzZTY0ZW5jb2RlLm9yZy8",
+          |  "ids": null
+          |}
+          |""".stripMargin) should equal(JsSuccess(expectedRequestObject))
+    }
+
+    "succeed when multiple ids" in {
+      val expectedRequestObject = QuotaGetRequest(
+        accountId = ACCOUNT_ID,
+        ids = Some(QuotaIds(List(UnparsedQuotaId("singleton"), 
UnparsedQuotaId("randomId")))),
+        properties = Some(Properties.empty()))
+
+      QuotaSerializer.deserializeQuotaGetRequest(
+        """
+          |{
+          |  "accountId": "aHR0cHM6Ly93d3cuYmFzZTY0ZW5jb2RlLm9yZy8",
+          |  "ids": ["singleton", "randomId"],
+          |  "properties": []
+          |}
+          |""".stripMargin) should equal(JsSuccess(expectedRequestObject))
+    }
+  }
+
+  "Serialize QuotaGetResponse" should {
+    "succeed" in {
+      val jmapQuota: JmapQuota = JmapQuota(
+        id = 
Id.validate("aHR0cHM6Ly93d3cuYmFzZTY0ZW5jb2RlLm9yZy8").toOption.get,
+        resourceType = CountResourceType,
+        used = UnsignedInt.liftOrThrow(1),
+        limit = UnsignedInt.liftOrThrow(2),
+        scope = AccountScope,
+        name = QuotaName("name1"),
+        dataTypes = List(MailDataType),
+        warnLimit = Some(UnsignedInt.liftOrThrow(123)),
+        softLimit = Some(UnsignedInt.liftOrThrow(456)),
+        description = Some(QuotaDescription("Description 1")))
+
+      val actualValue: QuotaGetResponse = QuotaGetResponse(
+        accountId = ACCOUNT_ID,
+        state = UuidState.INSTANCE,
+        list = List(jmapQuota),
+        notFound = QuotaNotFound(Set(UnparsedQuotaId("notfound2"))))
+
+      val expectedJson: String =
+        """{
+          |    "accountId": "aHR0cHM6Ly93d3cuYmFzZTY0ZW5jb2RlLm9yZy8",
+          |    "state": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+          |    "list": [
+          |        {
+          |            "id": "aHR0cHM6Ly93d3cuYmFzZTY0ZW5jb2RlLm9yZy8",
+          |            "resourceType": "count",
+          |            "used": 1,
+          |            "limit": 2,
+          |            "scope": "account",
+          |            "name": "name1",
+          |            "dataTypes": [
+          |                "Mail"
+          |            ],
+          |            "warnLimit": 123,
+          |            "softLimit": 456,
+          |            "description": "Description 1"
+          |        }
+          |    ],
+          |    "notFound": [
+          |        "notfound2"
+          |    ]
+          |}""".stripMargin
+
+      
assertThatJson(Json.stringify(QuotaSerializer.serialize(actualValue))).isEqualTo(expectedJson)
+    }
+
+    "succeed when list has multiple quota" in {
+      val jmapQuota: JmapQuota = JmapQuota(
+        id = 
Id.validate("aHR0cHM6Ly93d3cuYmFzZTY0ZW5jb2RlLm9yZy8").toOption.get,
+        resourceType = CountResourceType,
+        used = UnsignedInt.liftOrThrow(1),
+        limit = UnsignedInt.liftOrThrow(2),
+        scope = AccountScope,
+        name = QuotaName("name1"),
+        dataTypes = List(MailDataType),
+        description = None)
+
+      val jmapQuota2 = jmapQuota.copy(id = 
Id.validate("aHR0cHM6Ly93d3cuYmFzZTY0ZW5jb2RlLm9yZy9").toOption.get,
+        resourceType = OctetsResourceType,
+        scope = AccountScope,
+        name = QuotaName("name2"))
+
+      val actualValue: QuotaGetResponse = QuotaGetResponse(
+        accountId = ACCOUNT_ID,
+        state = UuidState.INSTANCE,
+        list = List(jmapQuota, jmapQuota2),
+        notFound = QuotaNotFound(Set()))
+
+      val expectedJson: String =
+        """{
+          |    "accountId": "aHR0cHM6Ly93d3cuYmFzZTY0ZW5jb2RlLm9yZy8",
+          |    "state": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+          |    "list": [
+          |        {
+          |            "id": "aHR0cHM6Ly93d3cuYmFzZTY0ZW5jb2RlLm9yZy8",
+          |            "resourceType": "count",
+          |            "used": 1,
+          |            "limit": 2,
+          |            "scope": "account",
+          |            "name": "name1",
+          |            "dataTypes": [
+          |                "Mail"
+          |            ]
+          |        },
+          |        {
+          |            "id": "aHR0cHM6Ly93d3cuYmFzZTY0ZW5jb2RlLm9yZy9",
+          |            "resourceType": "octets",
+          |            "used": 1,
+          |            "limit": 2,
+          |            "scope": "account",
+          |            "name": "name2",
+          |            "dataTypes": [
+          |                "Mail"
+          |            ]
+          |        }
+          |    ],
+          |    "notFound": []
+          |}""".stripMargin
+
+      
assertThatJson(Json.stringify(QuotaSerializer.serialize(actualValue))).isEqualTo(expectedJson)
+    }
+  }
+}


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

Reply via email to