This is an automated email from the ASF dual-hosted git repository.
hqtran 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 b4c8c62f80 JAMES-3539 Types field in push subscription should accept
null value (#2723)
b4c8c62f80 is described below
commit b4c8c62f80d8b7ffe131f0904639486aa05000b1
Author: Trần Hồng Quân <[email protected]>
AuthorDate: Tue May 20 09:45:22 2025 +0700
JAMES-3539 Types field in push subscription should accept null value (#2723)
According to the rfc
https://datatracker.ietf.org/doc/html/rfc8620#section-7.2 PushSubscription/set
should accept a null value for types, and in this case register the push ith
all the types available on the server.
We need to fix this to be RFC compliant.
---
.../CassandraPushSubscriptionRepository.java | 8 +-
.../CassandraPushSubscriptionRepositoryTest.java | 3 +-
.../PostgresPushSubscriptionRepository.java | 3 +-
.../MemoryPushSubscriptionRepository.java | 8 +-
.../james/jmap/api/change/TypeStateFactory.scala | 2 +-
.../james/jmap/api/model/PushSubscription.scala | 15 +-
.../MemoryPushSubscriptionRepositoryTest.java | 6 +-
.../PushDeleteUserDataTaskStepTest.scala | 9 +-
.../PushSubscriptionRepositoryContract.scala | 52 +++---
.../DistributedPushSubscriptionSetMethodTest.java | 3 +-
.../PushSubscriptionSetMethodContract.scala | 174 ++++++++++++++++++---
.../rfc8621/contract/probe/TypeStateProbe.scala} | 75 ++++-----
.../MemoryPushSubscriptionSetMethodTest.java | 3 +-
.../PostgresPushSubscriptionSetMethodTest.java | 2 +
.../james/jmap/core/PushSubscriptionSet.scala | 9 +-
.../method/PushSubscriptionUpdatePerformer.scala | 5 +-
.../jmap/pushsubscription/PushListenerTest.scala | 27 ++--
.../memory/MemoryUserDeletionIntegrationTest.java | 2 +-
18 files changed, 281 insertions(+), 125 deletions(-)
diff --git
a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/pushsubscription/CassandraPushSubscriptionRepository.java
b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/pushsubscription/CassandraPushSubscriptionRepository.java
index c17dfc807f..c9962a28e1 100644
---
a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/pushsubscription/CassandraPushSubscriptionRepository.java
+++
b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/pushsubscription/CassandraPushSubscriptionRepository.java
@@ -31,6 +31,7 @@ import java.util.Set;
import jakarta.inject.Inject;
import org.apache.james.core.Username;
+import org.apache.james.jmap.api.change.TypeStateFactory;
import org.apache.james.jmap.api.model.DeviceClientIdInvalidException;
import org.apache.james.jmap.api.model.ExpireTimeInvalidException;
import org.apache.james.jmap.api.model.InvalidPushSubscriptionKeys;
@@ -50,17 +51,20 @@ import scala.jdk.javaapi.OptionConverters;
public class CassandraPushSubscriptionRepository implements
PushSubscriptionRepository {
private final CassandraPushSubscriptionDAO dao;
private final Clock clock;
+ private final TypeStateFactory typeStateFactory;
@Inject
- public CassandraPushSubscriptionRepository(CassandraPushSubscriptionDAO
dao, Clock clock) {
+ public CassandraPushSubscriptionRepository(CassandraPushSubscriptionDAO
dao, Clock clock, TypeStateFactory typeStateFactory) {
this.dao = dao;
this.clock = clock;
+ this.typeStateFactory = typeStateFactory;
}
@Override
public Publisher<PushSubscription> save(Username username,
PushSubscriptionCreationRequest request) {
PushSubscription pushSubscription = PushSubscription.from(request,
-
evaluateExpiresTime(OptionConverters.toJava(request.expires().map(PushSubscriptionExpiredTime::value)),
clock));
+
evaluateExpiresTime(OptionConverters.toJava(request.expires().map(PushSubscriptionExpiredTime::value)),
clock),
+ typeStateFactory);
return isDuplicatedDeviceClientId(username, request.deviceClientId())
.handle((isDuplicated, sink) -> {
diff --git
a/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/pushsubscription/CassandraPushSubscriptionRepositoryTest.java
b/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/pushsubscription/CassandraPushSubscriptionRepositoryTest.java
index 24d5a1aa0c..1420d63779 100644
---
a/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/pushsubscription/CassandraPushSubscriptionRepositoryTest.java
+++
b/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/pushsubscription/CassandraPushSubscriptionRepositoryTest.java
@@ -47,7 +47,8 @@ public class CassandraPushSubscriptionRepositoryTest
implements PushSubscription
pushSubscriptionRepository = new CassandraPushSubscriptionRepository(
new CassandraPushSubscriptionDAO(cassandra.getConf(),
new
TypeStateFactory(CollectionConverters.asJava(PushSubscriptionRepositoryContract.TYPE_NAME_SET()))),
- clock);
+ clock,
+ new
TypeStateFactory(CollectionConverters.asJava(PushSubscriptionRepositoryContract.TYPE_NAME_SET())));
}
@Override
diff --git
a/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/pushsubscription/PostgresPushSubscriptionRepository.java
b/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/pushsubscription/PostgresPushSubscriptionRepository.java
index 9e48a2d421..0a6a342cf2 100644
---
a/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/pushsubscription/PostgresPushSubscriptionRepository.java
+++
b/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/pushsubscription/PostgresPushSubscriptionRepository.java
@@ -68,7 +68,8 @@ public class PostgresPushSubscriptionRepository implements
PushSubscriptionRepos
return validateCreationRequest(request)
.then(Mono.defer(() -> {
PushSubscription pushSubscription =
PushSubscription.from(request,
-
evaluateExpiresTime(OptionConverters.toJava(request.expires().map(PushSubscriptionExpiredTime::value)),
clock));
+
evaluateExpiresTime(OptionConverters.toJava(request.expires().map(PushSubscriptionExpiredTime::value)),
clock),
+ typeStateFactory);
return pushSubscriptionDAO.save(username, pushSubscription)
.thenReturn(pushSubscription);
diff --git
a/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/pushsubscription/MemoryPushSubscriptionRepository.java
b/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/pushsubscription/MemoryPushSubscriptionRepository.java
index 034d0e97d6..9ef8ccb279 100644
---
a/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/pushsubscription/MemoryPushSubscriptionRepository.java
+++
b/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/pushsubscription/MemoryPushSubscriptionRepository.java
@@ -32,6 +32,7 @@ import java.util.Set;
import jakarta.inject.Inject;
import org.apache.james.core.Username;
+import org.apache.james.jmap.api.change.TypeStateFactory;
import org.apache.james.jmap.api.model.DeviceClientIdInvalidException;
import org.apache.james.jmap.api.model.ExpireTimeInvalidException;
import org.apache.james.jmap.api.model.InvalidPushSubscriptionKeys;
@@ -55,11 +56,13 @@ import scala.jdk.javaapi.OptionConverters;
public class MemoryPushSubscriptionRepository implements
PushSubscriptionRepository {
private final Table<Username, PushSubscriptionId, PushSubscription> table;
private final Clock clock;
+ private final TypeStateFactory typeStateFactory;
@Inject
- public MemoryPushSubscriptionRepository(Clock clock) {
+ public MemoryPushSubscriptionRepository(Clock clock, TypeStateFactory
typeStateFactory) {
this.clock = clock;
this.table = HashBasedTable.create();
+ this.typeStateFactory = typeStateFactory;
}
@Override
@@ -78,7 +81,8 @@ public class MemoryPushSubscriptionRepository implements
PushSubscriptionReposit
})
.thenReturn(PushSubscription.from(request,
evaluateExpiresTime(OptionConverters.toJava(request.expires().map(PushSubscriptionExpiredTime::value)),
- clock)))
+ clock),
+ typeStateFactory))
.doOnNext(pushSubscription -> table.put(username,
pushSubscription.id(), pushSubscription));
}
diff --git
a/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/change/TypeStateFactory.scala
b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/change/TypeStateFactory.scala
index a2c49af270..47e5f94e8d 100644
---
a/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/change/TypeStateFactory.scala
+++
b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/change/TypeStateFactory.scala
@@ -27,7 +27,7 @@ import scala.jdk.CollectionConverters._
case class TypeStateFactory @Inject()(setTypeName: java.util.Set[TypeName]) {
val logger: Logger = LoggerFactory.getLogger(classOf[TypeStateFactory])
- val all: scala.collection.mutable.Set[TypeName] = setTypeName.asScala
+ val all: Set[TypeName] = setTypeName.asScala.toSet
def strictParse(string: String): Either[IllegalArgumentException, TypeName] =
all.flatMap(_.parse(string))
diff --git
a/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/model/PushSubscription.scala
b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/model/PushSubscription.scala
index 011345df9f..9055beee4b 100644
---
a/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/model/PushSubscription.scala
+++
b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/model/PushSubscription.scala
@@ -28,6 +28,7 @@ import java.util.{Base64, UUID}
import com.google.crypto.tink.HybridEncrypt
import com.google.crypto.tink.apps.webpush.WebPushHybridEncrypt
import com.google.crypto.tink.subtle.EllipticCurves
+import org.apache.james.jmap.api.change.TypeStateFactory
import
org.apache.james.jmap.api.model.ExpireTimeInvalidException.TIME_FORMATTER
import scala.util.Try
@@ -96,7 +97,7 @@ case class PushSubscriptionCreationRequest(deviceClientId:
DeviceClientId,
url: PushSubscriptionServerURL,
keys: Option[PushSubscriptionKeys]
= None,
expires:
Option[PushSubscriptionExpiredTime] = None,
- types: Seq[TypeName]) {
+ types: Option[Seq[TypeName]] =
None) {
def validate: Either[IllegalArgumentException,
PushSubscriptionCreationRequest] =
for {
@@ -107,10 +108,9 @@ case class PushSubscriptionCreationRequest(deviceClientId:
DeviceClientId,
}
private def validateTypes: Either[IllegalArgumentException,
PushSubscriptionCreationRequest] =
- if (types.isEmpty) {
- scala.Left(new IllegalArgumentException("types must not be empty"))
- } else {
- Right(this)
+ types match {
+ case Some(Seq()) => scala.Left(new IllegalArgumentException("types must
not be empty"))
+ case _ => Right(this)
}
private def validateKeys: Either[IllegalArgumentException,
PushSubscriptionCreationRequest] =
@@ -123,7 +123,8 @@ object PushSubscription {
val EXPIRES_TIME_MAX_DAY: Int = 7
def from(creationRequest: PushSubscriptionCreationRequest,
- expireTime: PushSubscriptionExpiredTime): PushSubscription =
+ expireTime: PushSubscriptionExpiredTime,
+ typeStateFactory: TypeStateFactory): PushSubscription =
PushSubscription(id = PushSubscriptionId.generate(),
deviceClientId = creationRequest.deviceClientId,
url = creationRequest.url,
@@ -131,7 +132,7 @@ object PushSubscription {
verificationCode = VerificationCode.generate(),
validated = !VALIDATED,
expires = expireTime,
- types = creationRequest.types)
+ types = creationRequest.types.getOrElse(typeStateFactory.all.toSeq))
}
case class PushSubscription(id: PushSubscriptionId,
diff --git
a/server/data/data-jmap/src/test/java/org/apache/james/jmap/memory/pushsubscription/MemoryPushSubscriptionRepositoryTest.java
b/server/data/data-jmap/src/test/java/org/apache/james/jmap/memory/pushsubscription/MemoryPushSubscriptionRepositoryTest.java
index 19c38c5ed4..3d06cacaf8 100644
---
a/server/data/data-jmap/src/test/java/org/apache/james/jmap/memory/pushsubscription/MemoryPushSubscriptionRepositoryTest.java
+++
b/server/data/data-jmap/src/test/java/org/apache/james/jmap/memory/pushsubscription/MemoryPushSubscriptionRepositoryTest.java
@@ -19,11 +19,14 @@
package org.apache.james.jmap.memory.pushsubscription;
+import org.apache.james.jmap.api.change.TypeStateFactory;
import org.apache.james.jmap.api.pushsubscription.PushSubscriptionRepository;
import
org.apache.james.jmap.api.pushsubscription.PushSubscriptionRepositoryContract;
import org.apache.james.utils.UpdatableTickingClock;
import org.junit.jupiter.api.BeforeEach;
+import scala.jdk.javaapi.CollectionConverters;
+
public class MemoryPushSubscriptionRepositoryTest implements
PushSubscriptionRepositoryContract {
UpdatableTickingClock clock;
PushSubscriptionRepository pushSubscriptionRepository;
@@ -31,7 +34,8 @@ public class MemoryPushSubscriptionRepositoryTest implements
PushSubscriptionRep
@BeforeEach
void setup() {
clock = new
UpdatableTickingClock(PushSubscriptionRepositoryContract.NOW());
- pushSubscriptionRepository = new
MemoryPushSubscriptionRepository(clock);
+ pushSubscriptionRepository = new
MemoryPushSubscriptionRepository(clock,
+ new
TypeStateFactory(CollectionConverters.asJava(PushSubscriptionRepositoryContract.TYPE_NAME_SET())));
}
@Override
diff --git
a/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/pushsubscription/PushDeleteUserDataTaskStepTest.scala
b/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/pushsubscription/PushDeleteUserDataTaskStepTest.scala
index c039ca8d83..0a557fa5e6 100644
---
a/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/pushsubscription/PushDeleteUserDataTaskStepTest.scala
+++
b/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/pushsubscription/PushDeleteUserDataTaskStepTest.scala
@@ -19,9 +19,10 @@
package org.apache.james.jmap.api.pushsubscription
-import java.net.{URI, URL}
+import java.net.URI
import java.time.Clock
+import org.apache.james.jmap.api.change.TypeStateFactory
import org.apache.james.jmap.api.identity.CustomIdentityDAOContract.bob
import org.apache.james.jmap.api.model.{DeviceClientId,
PushSubscriptionCreationRequest, PushSubscriptionServerURL}
import
org.apache.james.jmap.api.pushsubscription.PushSubscriptionRepositoryContract.ALICE
@@ -31,13 +32,15 @@ import org.junit.jupiter.api.{BeforeEach, Test}
import reactor.core.publisher.Flux
import reactor.core.scala.publisher.SMono
+import scala.jdk.javaapi.CollectionConverters
+
class PushDeleteUserDataTaskStepTest {
var pushSubscriptionRepository: PushSubscriptionRepository = _
var testee: PushDeleteUserDataTaskStep = _
@BeforeEach
def beforeEach(): Unit = {
- pushSubscriptionRepository = new
MemoryPushSubscriptionRepository(Clock.systemUTC())
+ pushSubscriptionRepository = new
MemoryPushSubscriptionRepository(Clock.systemUTC(),
TypeStateFactory(CollectionConverters.asJava(PushSubscriptionRepositoryContract.TYPE_NAME_SET)))
testee = new PushDeleteUserDataTaskStep(pushSubscriptionRepository)
}
@@ -52,7 +55,7 @@ class PushDeleteUserDataTaskStepTest {
val validRequest = PushSubscriptionCreationRequest(
deviceClientId = DeviceClientId("1"),
url = PushSubscriptionServerURL(new
URI("https://example.com/push").toURL()),
- types = Seq(CustomTypeName1))
+ types = Some(Seq(CustomTypeName1)))
SMono.fromPublisher(pushSubscriptionRepository.save(ALICE,
validRequest)).block().id
SMono(testee.deleteUserData(ALICE)).block()
diff --git
a/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepositoryContract.scala
b/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepositoryContract.scala
index 7b2caba10c..ff3d2ee50a 100644
---
a/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepositoryContract.scala
+++
b/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepositoryContract.scala
@@ -80,7 +80,7 @@ trait PushSubscriptionRepositoryContract {
val validRequest = PushSubscriptionCreationRequest(
deviceClientId = DeviceClientId("1"),
url = PushSubscriptionServerURL(new
URI("https://example.com/push").toURL()),
- types = Seq(CustomTypeName1))
+ types = Some(Seq(CustomTypeName1)))
val pushSubscriptionId = SMono.fromPublisher(testee.save(ALICE,
validRequest)).block().id
val singleRecordSaved = SFlux.fromPublisher(testee.get(ALICE,
Set(pushSubscriptionId).asJava)).count().block()
@@ -92,7 +92,7 @@ trait PushSubscriptionRepositoryContract {
val validRequest = PushSubscriptionCreationRequest(
deviceClientId = DeviceClientId("1"),
url = PushSubscriptionServerURL(new
URI("https://example.com/push").toURL()),
- types = Seq(CustomTypeName1))
+ types = Some(Seq(CustomTypeName1)))
val pushSubscriptionId = SMono.fromPublisher(testee.save(ALICE,
validRequest)).block().id
val newSavedSubscription = SFlux.fromPublisher(testee.get(ALICE,
Set(pushSubscriptionId).asJava)).blockFirst().get
@@ -105,7 +105,7 @@ trait PushSubscriptionRepositoryContract {
deviceClientId = DeviceClientId("1"),
url = PushSubscriptionServerURL(new
URI("https://example.com/push").toURL()),
expires = Some(PushSubscriptionExpiredTime(VALID_EXPIRE.plusDays(8))),
- types = Seq(CustomTypeName1))
+ types = Some(Seq(CustomTypeName1)))
val pushSubscriptionId = SMono.fromPublisher(testee.save(ALICE,
request)).block().id
val newSavedSubscription = SFlux.fromPublisher(testee.get(ALICE,
Set(pushSubscriptionId).asJava)).blockFirst().get
@@ -118,7 +118,7 @@ trait PushSubscriptionRepositoryContract {
deviceClientId = DeviceClientId("1"),
url = PushSubscriptionServerURL(new
URI("https://example.com/push").toURL()),
expires = Some(PushSubscriptionExpiredTime(INVALID_EXPIRE)),
- types = Seq(CustomTypeName1))
+ types = Some(Seq(CustomTypeName1)))
assertThatThrownBy(() => SMono.fromPublisher(testee.save(ALICE,
invalidRequest)).block())
.isInstanceOf(classOf[ExpireTimeInvalidException])
@@ -129,13 +129,13 @@ trait PushSubscriptionRepositoryContract {
val firstRequest = PushSubscriptionCreationRequest(
deviceClientId = DeviceClientId("1"),
url = PushSubscriptionServerURL(new
URI("https://example.com/push").toURL()),
- types = Seq(CustomTypeName1))
+ types = Some(Seq(CustomTypeName1)))
SMono.fromPublisher(testee.save(ALICE, firstRequest)).block()
val secondRequestWithDuplicatedDeviceClientId =
PushSubscriptionCreationRequest(
deviceClientId = DeviceClientId("1"),
url = PushSubscriptionServerURL(new
URI("https://example.com/push").toURL()),
- types = Seq(CustomTypeName1))
+ types = Some(Seq(CustomTypeName1)))
assertThatThrownBy(() => SMono.fromPublisher(testee.save(ALICE,
secondRequestWithDuplicatedDeviceClientId)).block())
.isInstanceOf(classOf[DeviceClientIdInvalidException])
@@ -146,7 +146,7 @@ trait PushSubscriptionRepositoryContract {
val validRequest = PushSubscriptionCreationRequest(
deviceClientId = DeviceClientId("1"),
url = PushSubscriptionServerURL(new
URI("https://example.com/push").toURL()),
- types = Seq(CustomTypeName1))
+ types = Some(Seq(CustomTypeName1)))
val pushSubscriptionId = SMono.fromPublisher(testee.save(ALICE,
validRequest)).block().id
assertThatThrownBy(() =>
SMono.fromPublisher(testee.updateExpireTime(ALICE, pushSubscriptionId,
INVALID_EXPIRE)).block())
@@ -158,7 +158,7 @@ trait PushSubscriptionRepositoryContract {
val validRequest = PushSubscriptionCreationRequest(
deviceClientId = DeviceClientId("1"),
url = PushSubscriptionServerURL(new
URI("https://example.com/push").toURL()),
- types = Seq(CustomTypeName1))
+ types = Some(Seq(CustomTypeName1)))
val pushSubscriptionId = SMono.fromPublisher(testee.save(ALICE,
validRequest)).block().id
SMono.fromPublisher(testee.updateExpireTime(ALICE, pushSubscriptionId,
MAX_EXPIRE.plusDays(1))).block()
@@ -179,7 +179,7 @@ trait PushSubscriptionRepositoryContract {
val validRequest = PushSubscriptionCreationRequest(
deviceClientId = DeviceClientId("1"),
url = PushSubscriptionServerURL(new
URI("https://example.com/push").toURL()),
- types = Seq(CustomTypeName1))
+ types = Some(Seq(CustomTypeName1)))
val pushSubscriptionId = SMono.fromPublisher(testee.save(ALICE,
validRequest)).block().id
SMono.fromPublisher(testee.updateExpireTime(ALICE, pushSubscriptionId,
VALID_EXPIRE)).block()
@@ -192,7 +192,7 @@ trait PushSubscriptionRepositoryContract {
val validRequest = PushSubscriptionCreationRequest(
deviceClientId = DeviceClientId("1"),
url = PushSubscriptionServerURL(new
URI("https://example.com/push").toURL()),
- types = Seq(CustomTypeName1))
+ types = Some(Seq(CustomTypeName1)))
val pushSubscriptionId = SMono.fromPublisher(testee.save(ALICE,
validRequest)).block().id
val fixedExpires = SMono.fromPublisher(testee.updateExpireTime(ALICE,
pushSubscriptionId, MAX_EXPIRE.plusDays(1))).block()
@@ -204,7 +204,7 @@ trait PushSubscriptionRepositoryContract {
val validRequest = PushSubscriptionCreationRequest(
deviceClientId = DeviceClientId("1"),
url = PushSubscriptionServerURL(new
URI("https://example.com/push").toURL()),
- types = Seq(CustomTypeName1))
+ types = Some(Seq(CustomTypeName1)))
val pushSubscriptionId = SMono.fromPublisher(testee.save(ALICE,
validRequest)).block().id
val newTypes: Set[TypeName] = Set(CustomTypeName1, CustomTypeName2)
@@ -236,7 +236,7 @@ trait PushSubscriptionRepositoryContract {
val validRequest = PushSubscriptionCreationRequest(
deviceClientId = DeviceClientId("1"),
url = PushSubscriptionServerURL(new
URI("https://example.com/push").toURL()),
- types = Seq(CustomTypeName1))
+ types = Some(Seq(CustomTypeName1)))
val pushSubscriptionId = SMono.fromPublisher(testee.save(ALICE,
validRequest)).block().id
val singleRecordSaved = SFlux.fromPublisher(testee.get(ALICE,
Set(pushSubscriptionId).asJava)).count().block()
assertThat(singleRecordSaved).isEqualTo(1)
@@ -252,7 +252,7 @@ trait PushSubscriptionRepositoryContract {
val validRequest = PushSubscriptionCreationRequest(
deviceClientId = DeviceClientId("1"),
url = PushSubscriptionServerURL(new
URI("https://example.com/push").toURL()),
- types = Seq(CustomTypeName1))
+ types = Some(Seq(CustomTypeName1)))
val pushSubscriptionId = SMono.fromPublisher(testee.save(ALICE,
validRequest)).block().id
val singleRecordSaved = SFlux.fromPublisher(testee.get(ALICE,
Set(pushSubscriptionId).asJava)).count().block()
assertThat(singleRecordSaved).isEqualTo(1)
@@ -285,12 +285,12 @@ trait PushSubscriptionRepositoryContract {
deviceClientId = deviceClientId1,
url = PushSubscriptionServerURL(new
URI("https://example.com/push").toURL()),
expires = Option(PushSubscriptionExpiredTime(VALID_EXPIRE)),
- types = Seq(CustomTypeName1))
+ types = Some(Seq(CustomTypeName1)))
val validRequest2 = PushSubscriptionCreationRequest(
deviceClientId = deviceClientId2,
url = PushSubscriptionServerURL(new
URI("https://example.com/push").toURL()),
expires = Option(PushSubscriptionExpiredTime(VALID_EXPIRE)),
- types = Seq(CustomTypeName2))
+ types = Some(Seq(CustomTypeName2)))
val pushSubscriptionId1 = SMono.fromPublisher(testee.save(ALICE,
validRequest1)).block().id
val pushSubscriptionId2 = SMono.fromPublisher(testee.save(ALICE,
validRequest2)).block().id
@@ -306,7 +306,7 @@ trait PushSubscriptionRepositoryContract {
deviceClientId = deviceClientId1,
url = PushSubscriptionServerURL(new
URI("https://example.com/push").toURL()),
expires = Option(PushSubscriptionExpiredTime(VALID_EXPIRE)),
- types = Seq(CustomTypeName1))
+ types = Some(Seq(CustomTypeName1)))
val pushSubscriptionId1 = SMono.fromPublisher(testee.save(ALICE,
validRequest1)).block().id
val pushSubscriptionId2 = PushSubscriptionId.generate()
@@ -321,7 +321,7 @@ trait PushSubscriptionRepositoryContract {
deviceClientId = DeviceClientId("1"),
url = PushSubscriptionServerURL(new
URI("https://example.com/push").toURL()),
expires = Option(PushSubscriptionExpiredTime(VALID_EXPIRE.plusDays(1))),
- types = Seq(CustomTypeName1))
+ types = Some(Seq(CustomTypeName1)))
val pushSubscriptionId = SMono.fromPublisher(testee.save(ALICE,
validRequest1)).block().id
clock.setInstant(VALID_EXPIRE.plusDays(2).toInstant)
@@ -338,11 +338,11 @@ trait PushSubscriptionRepositoryContract {
val validRequest1 = PushSubscriptionCreationRequest(
deviceClientId = deviceClientId1,
url = PushSubscriptionServerURL(new
URI("https://example.com/push").toURL()),
- types = Seq(CustomTypeName1))
+ types = Some(Seq(CustomTypeName1)))
val validRequest2 = PushSubscriptionCreationRequest(
deviceClientId = deviceClientId2,
url = PushSubscriptionServerURL(new
URI("https://example.com/push").toURL()),
- types = Seq(CustomTypeName2))
+ types = Some(Seq(CustomTypeName2)))
val pushSubscriptionId1: PushSubscriptionId =
SMono.fromPublisher(testee.save(ALICE, validRequest1)).block().id
val pushSubscriptionId2: PushSubscriptionId =
SMono.fromPublisher(testee.save(ALICE, validRequest2)).block().id
@@ -360,7 +360,7 @@ trait PushSubscriptionRepositoryContract {
deviceClientId = DeviceClientId("1"),
url = PushSubscriptionServerURL(new
URI("https://example.com/push").toURL()),
expires = Option(PushSubscriptionExpiredTime(VALID_EXPIRE.plusDays(1))),
- types = Seq(CustomTypeName1))
+ types = Some(Seq(CustomTypeName1)))
val pushSubscriptionId = SMono.fromPublisher(testee.save(ALICE,
validRequest1)).block().id
clock.setInstant(VALID_EXPIRE.plusDays(2).toInstant)
@@ -375,7 +375,7 @@ trait PushSubscriptionRepositoryContract {
val validRequest = PushSubscriptionCreationRequest(
deviceClientId = DeviceClientId("1"),
url = PushSubscriptionServerURL(new
URI("https://example.com/push").toURL()),
- types = Seq(CustomTypeName1))
+ types = Some(Seq(CustomTypeName1)))
val pushSubscriptionId = SMono.fromPublisher(testee.save(ALICE,
validRequest)).block().id
SMono.fromPublisher(testee.validateVerificationCode(ALICE,
pushSubscriptionId)).block()
@@ -397,7 +397,7 @@ trait PushSubscriptionRepositoryContract {
val validRequest = PushSubscriptionCreationRequest(
deviceClientId = DeviceClientId("1"),
url = PushSubscriptionServerURL(new
URI("https://example.com/push").toURL()),
- types = Seq(CustomTypeName1),
+ types = Some(Seq(CustomTypeName1)),
keys = fullKeyPair)
val pushSubscriptionId1 = SMono.fromPublisher(testee.save(ALICE,
validRequest)).block().id
@@ -413,7 +413,7 @@ trait PushSubscriptionRepositoryContract {
val validRequest = PushSubscriptionCreationRequest(
deviceClientId = DeviceClientId("1"),
url = PushSubscriptionServerURL(new
URI("https://example.com/push").toURL()),
- types = Seq(CustomTypeName1),
+ types = Some(Seq(CustomTypeName1)),
keys = emptyKeyPair)
val pushSubscriptionId1 = SMono.fromPublisher(testee.save(ALICE,
validRequest)).block().id
@@ -429,7 +429,7 @@ trait PushSubscriptionRepositoryContract {
val validRequest = PushSubscriptionCreationRequest(
deviceClientId = DeviceClientId("1"),
url = PushSubscriptionServerURL(new
URI("https://example.com/push").toURL()),
- types = Seq(CustomTypeName1),
+ types = Some(Seq(CustomTypeName1)),
keys = emptyP256hKey)
assertThatThrownBy(() => SMono.fromPublisher(testee.save(ALICE,
validRequest)).block())
@@ -443,7 +443,7 @@ trait PushSubscriptionRepositoryContract {
val validRequest = PushSubscriptionCreationRequest(
deviceClientId = DeviceClientId("1"),
url = PushSubscriptionServerURL(new
URI("https://example.com/push").toURL),
- types = Seq(CustomTypeName1),
+ types = Some(Seq(CustomTypeName1)),
keys = emptyAuthKey)
assertThatThrownBy(() => SMono.fromPublisher(testee.save(ALICE,
validRequest)).block())
@@ -455,7 +455,7 @@ trait PushSubscriptionRepositoryContract {
val validRequest = PushSubscriptionCreationRequest(
deviceClientId = DeviceClientId("1"),
url = PushSubscriptionServerURL(new URL("https://example.com/push")),
- types = Seq(CustomTypeName1))
+ types = Some(Seq(CustomTypeName1)))
val pushSubscriptionId = SMono.fromPublisher(testee.save(ALICE,
validRequest)).block().id
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/DistributedPushSubscriptionSetMethodTest.java
b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedPushSubscriptionSetMethodTest.java
index 8df62b6da6..1e29afb110 100644
---
a/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedPushSubscriptionSetMethodTest.java
+++
b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedPushSubscriptionSetMethodTest.java
@@ -30,6 +30,7 @@ import
org.apache.james.jmap.pushsubscription.PushClientConfiguration;
import org.apache.james.jmap.rfc8621.contract.PushServerExtension;
import org.apache.james.jmap.rfc8621.contract.PushSubscriptionProbeModule;
import
org.apache.james.jmap.rfc8621.contract.PushSubscriptionSetMethodContract;
+import org.apache.james.jmap.rfc8621.contract.probe.TypeStateModule;
import org.apache.james.modules.AwsS3BlobStoreExtension;
import org.apache.james.modules.RabbitMQExtension;
import org.apache.james.modules.TestJMAPServerModule;
@@ -54,7 +55,7 @@ public class DistributedPushSubscriptionSetMethodTest
implements PushSubscriptio
.extension(new AwsS3BlobStoreExtension())
.extension(new ClockExtension())
.server(configuration ->
CassandraRabbitMQJamesServerMain.createServer(configuration)
- .overrideWith(new TestJMAPServerModule(), new
PushSubscriptionProbeModule())
+ .overrideWith(new TestJMAPServerModule(), new
PushSubscriptionProbeModule(), new TypeStateModule())
.overrideWith(binder ->
binder.bind(PushClientConfiguration.class).toInstance(PushClientConfiguration.UNSAFE_DEFAULT())))
.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/PushSubscriptionSetMethodContract.scala
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/PushSubscriptionSetMethodContract.scala
index 053d2fbf28..6f8bc99a40 100644
---
a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/PushSubscriptionSetMethodContract.scala
+++
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/PushSubscriptionSetMethodContract.scala
@@ -36,6 +36,7 @@ import com.google.inject.multibindings.Multibinder
import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
import io.restassured.RestAssured.{`given`, requestSpecification}
import io.restassured.http.ContentType.JSON
+import io.restassured.path.json.JsonPath
import jakarta.inject.Inject
import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
import net.javacrumbs.jsonunit.core.Option.IGNORING_ARRAY_ORDER
@@ -50,6 +51,7 @@ import org.apache.james.jmap.core.UTCDate
import org.apache.james.jmap.http.UserCredential
import
org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER,
BOB, BOB_PASSWORD, DOMAIN, authScheme, baseRequestSpecBuilder}
import
org.apache.james.jmap.rfc8621.contract.PushSubscriptionSetMethodContract.TIME_FORMATTER
+import org.apache.james.jmap.rfc8621.contract.probe.TypeStateProbe
import org.apache.james.utils.{DataProbeImpl, GuiceProbe,
UpdatableTickingClock}
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.SoftAssertions
@@ -72,7 +74,7 @@ class PushSubscriptionProbe
@Inject()(pushSubscriptionRepository: PushSubscripti
SMono(pushSubscriptionRepository.save(username,
PushSubscriptionCreationRequest(
deviceClientId = deviceId,
url = url,
- types = types)))
+ types = Some(types))))
.block()
def retrievePushSubscription(username: Username, id: PushSubscriptionId):
PushSubscription =
@@ -257,6 +259,128 @@ trait PushSubscriptionSetMethodContract {
.containsExactlyInAnyOrder(MailboxTypeName, EmailTypeName)
}
+ @Test
+ def updateWithMissingTypesPropertyShouldNotUpdateTypes(server:
GuiceJamesServer): Unit = {
+ val probe = server.getProbe(classOf[PushSubscriptionProbe])
+ val pushSubscription = probe
+ .createPushSubscription(username = BOB,
+ url = PushSubscriptionServerURL(new
URI("https://example.com/push/?device=X8980fc&client=12c6d086").toURL),
+ deviceId = DeviceClientId("12c6d086"),
+ types = Seq(MailboxTypeName))
+
+ val validExpiresString =
UTCDate(ZonedDateTime.now().plusDays(1)).asUTC.format(TIME_FORMATTER)
+ val request: String =
+ s"""{
+ | "using": ["urn:ietf:params:jmap:core"],
+ | "methodCalls": [
+ | [
+ | "PushSubscription/set",
+ | {
+ | "update": {
+ | "${pushSubscription.id.serialise}": {
+ | "expires": "$validExpiresString"
+ | }
+ | }
+ | },
+ | "c1"
+ | ]
+ | ]
+ | }""".stripMargin
+
+ val response: String = `given`
+ .body(request)
+ .when
+ .post
+ .`then`
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract
+ .body
+ .asString
+
+ assertThatJson(response)
+ .isEqualTo(
+ s"""{
+ | "sessionState": "${SESSION_STATE.value}",
+ | "methodResponses": [
+ | [
+ | "PushSubscription/set",
+ | {
+ | "updated": {
+ | "${pushSubscription.id.serialise}": {}
+ | }
+ | },
+ | "c1"
+ | ]
+ | ]
+ |}""".stripMargin)
+
+ // Types are not updated
+ assertThat(probe.retrievePushSubscription(BOB,
pushSubscription.id).types.asJava)
+ .containsOnly(MailboxTypeName)
+ }
+
+ @Test
+ def updateShouldAcceptNullTypesAndUpdateToAllTypes(server:
GuiceJamesServer): Unit = {
+ val subscriptionProbe = server.getProbe(classOf[PushSubscriptionProbe])
+ val typeStateProbe = server.getProbe(classOf[TypeStateProbe])
+ val pushSubscription = subscriptionProbe
+ .createPushSubscription(username = BOB,
+ url = PushSubscriptionServerURL(new
URI("https://example.com/push/?device=X8980fc&client=12c6d086").toURL),
+ deviceId = DeviceClientId("12c6d086"),
+ types = Seq(MailboxTypeName))
+
+ val request: String =
+ s"""{
+ | "using": ["urn:ietf:params:jmap:core"],
+ | "methodCalls": [
+ | [
+ | "PushSubscription/set",
+ | {
+ | "update": {
+ | "${pushSubscription.id.serialise}": {
+ | "types": null
+ | }
+ | }
+ | },
+ | "c1"
+ | ]
+ | ]
+ | }""".stripMargin
+
+ val response: String = `given`
+ .body(request)
+ .when
+ .post
+ .`then`
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract
+ .body
+ .asString
+
+ assertThatJson(response)
+ .isEqualTo(
+ s"""{
+ | "sessionState": "${SESSION_STATE.value}",
+ | "methodResponses": [
+ | [
+ | "PushSubscription/set",
+ | {
+ | "updated": {
+ | "${pushSubscription.id.serialise}": {}
+ | }
+ | },
+ | "c1"
+ | ]
+ | ]
+ |}""".stripMargin)
+
+ // All types are registered
+ assertThat(subscriptionProbe.retrievePushSubscription(BOB,
pushSubscription.id).types.asJava)
+ .containsExactlyInAnyOrderElementsOf(typeStateProbe.typesNames())
+ }
+
@Test
def updateShouldRejectUnknownTypes(server: GuiceJamesServer): Unit = {
val probe = server.getProbe(classOf[PushSubscriptionProbe])
@@ -438,25 +562,31 @@ trait PushSubscriptionSetMethodContract {
}
@Test
- def setMethodShouldNotCreatedWhenMissingTypesPropertyInCreationRequest():
Unit = {
+ def setCreateShouldAcceptNullTypesPropertyInCreationRequest(server:
GuiceJamesServer, pushServer: ClientAndServer): Unit = {
+ val subscriptionProbe = server.getProbe(classOf[PushSubscriptionProbe])
+ val typeStateProbe = server.getProbe(classOf[TypeStateProbe])
+
val request: String =
- """{
- | "using": ["urn:ietf:params:jmap:core"],
+ s"""{
+ | "using": [
+ | "urn:ietf:params:jmap:core"
+ | ],
| "methodCalls": [
- | [
- | "PushSubscription/set",
- | {
- | "create": {
- | "4f29": {
- | "deviceClientId": "a889-ffea-910",
- | "url":
"https://example.com/push/?device=X8980fc&client=12c6d086"
+ | [
+ | "PushSubscription/set",
+ | {
+ | "create": {
+ | "4f29": {
+ | "deviceClientId": "a889-ffea-910",
+ | "url": "${getPushServerUrl(pushServer)}",
+ | "expires":
"${UTCDate(ZonedDateTime.now().plusDays(1)).asUTC.format(TIME_FORMATTER)}"
+ | }
| }
- | }
- | },
- | "c1"
- | ]
+ | },
+ | "c1"
+ | ]
| ]
- | }""".stripMargin
+ |}""".stripMargin
val response: String = `given`
.body(request)
@@ -477,10 +607,10 @@ trait PushSubscriptionSetMethodContract {
| [
| "PushSubscription/set",
| {
- | "notCreated": {
+ | "created": {
| "4f29": {
- | "type": "invalidArguments",
- | "description": "Missing '/types' property"
+ | "id": "$${json-unit.ignore}",
+ | "expires": "$${json-unit.ignore}"
| }
| }
| },
@@ -488,6 +618,12 @@ trait PushSubscriptionSetMethodContract {
| ]
| ]
|}""".stripMargin)
+
+ // All types are registered
+ val subscriptionId: PushSubscriptionId =
PushSubscriptionId.parse(JsonPath.from(response)
+ .getString("methodResponses[0][1].created.4f29.id")).toOption.get
+ assertThat(subscriptionProbe.retrievePushSubscription(BOB,
subscriptionId).types.asJava)
+ .containsExactlyInAnyOrderElementsOf(typeStateProbe.typesNames())
}
@Test
diff --git
a/server/data/data-jmap/src/test/java/org/apache/james/jmap/memory/pushsubscription/MemoryPushSubscriptionRepositoryTest.java
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/probe/TypeStateProbe.scala
similarity index 51%
copy from
server/data/data-jmap/src/test/java/org/apache/james/jmap/memory/pushsubscription/MemoryPushSubscriptionRepositoryTest.java
copy to
server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/probe/TypeStateProbe.scala
index 19c38c5ed4..85cce3c696 100644
---
a/server/data/data-jmap/src/test/java/org/apache/james/jmap/memory/pushsubscription/MemoryPushSubscriptionRepositoryTest.java
+++
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/probe/TypeStateProbe.scala
@@ -1,46 +1,39 @@
-/******************************************************************
- * 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. *
- ******************************************************************/
+/****************************************************************
+ * 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.memory.pushsubscription;
+package org.apache.james.jmap.rfc8621.contract.probe
-import org.apache.james.jmap.api.pushsubscription.PushSubscriptionRepository;
-import
org.apache.james.jmap.api.pushsubscription.PushSubscriptionRepositoryContract;
-import org.apache.james.utils.UpdatableTickingClock;
-import org.junit.jupiter.api.BeforeEach;
+import com.google.inject.AbstractModule
+import com.google.inject.multibindings.Multibinder
+import jakarta.inject.Inject
+import org.apache.james.jmap.api.change.TypeStateFactory
+import org.apache.james.jmap.api.model.TypeName
+import org.apache.james.utils.GuiceProbe
-public class MemoryPushSubscriptionRepositoryTest implements
PushSubscriptionRepositoryContract {
- UpdatableTickingClock clock;
- PushSubscriptionRepository pushSubscriptionRepository;
-
- @BeforeEach
- void setup() {
- clock = new
UpdatableTickingClock(PushSubscriptionRepositoryContract.NOW());
- pushSubscriptionRepository = new
MemoryPushSubscriptionRepository(clock);
- }
-
- @Override
- public UpdatableTickingClock clock() {
- return clock;
- }
+class TypeStateModule extends AbstractModule {
+ override def configure(): Unit =
+ Multibinder.newSetBinder(binder(), classOf[GuiceProbe])
+ .addBinding()
+ .to(classOf[TypeStateProbe])
+}
- @Override
- public PushSubscriptionRepository testee() {
- return pushSubscriptionRepository;
- }
+class TypeStateProbe @Inject()(typeStateFactory: TypeStateFactory) extends
GuiceProbe {
+ def typesNames(): java.util.Set[TypeName] =
+ typeStateFactory.setTypeName
}
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/MemoryPushSubscriptionSetMethodTest.java
b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryPushSubscriptionSetMethodTest.java
index 96f12a6cf1..e88a3c4ee3 100644
---
a/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryPushSubscriptionSetMethodTest.java
+++
b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryPushSubscriptionSetMethodTest.java
@@ -30,6 +30,7 @@ import
org.apache.james.jmap.pushsubscription.PushClientConfiguration;
import org.apache.james.jmap.rfc8621.contract.PushServerExtension;
import org.apache.james.jmap.rfc8621.contract.PushSubscriptionProbeModule;
import
org.apache.james.jmap.rfc8621.contract.PushSubscriptionSetMethodContract;
+import org.apache.james.jmap.rfc8621.contract.probe.TypeStateModule;
import org.apache.james.modules.TestJMAPServerModule;
import org.junit.jupiter.api.extension.RegisterExtension;
@@ -43,7 +44,7 @@ public class MemoryPushSubscriptionSetMethodTest implements
PushSubscriptionSetM
.build())
.extension(new ClockExtension())
.server(configuration ->
MemoryJamesServerMain.createServer(configuration)
- .overrideWith(new TestJMAPServerModule(), new
PushSubscriptionProbeModule())
+ .overrideWith(new TestJMAPServerModule(), new
PushSubscriptionProbeModule(), new TypeStateModule())
.overrideWith(binder ->
binder.bind(PushClientConfiguration.class).toInstance(PushClientConfiguration.UNSAFE_DEFAULT())))
.build();
diff --git
a/server/protocols/jmap-rfc-8621-integration-tests/postgres-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/postgres/PostgresPushSubscriptionSetMethodTest.java
b/server/protocols/jmap-rfc-8621-integration-tests/postgres-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/postgres/PostgresPushSubscriptionSetMethodTest.java
index 06ba0f85e9..54132d42cb 100644
---
a/server/protocols/jmap-rfc-8621-integration-tests/postgres-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/postgres/PostgresPushSubscriptionSetMethodTest.java
+++
b/server/protocols/jmap-rfc-8621-integration-tests/postgres-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/postgres/PostgresPushSubscriptionSetMethodTest.java
@@ -32,6 +32,7 @@ import
org.apache.james.jmap.pushsubscription.PushClientConfiguration;
import org.apache.james.jmap.rfc8621.contract.PushServerExtension;
import org.apache.james.jmap.rfc8621.contract.PushSubscriptionProbeModule;
import
org.apache.james.jmap.rfc8621.contract.PushSubscriptionSetMethodContract;
+import org.apache.james.jmap.rfc8621.contract.probe.TypeStateModule;
import org.apache.james.modules.RabbitMQExtension;
import org.apache.james.modules.TestJMAPServerModule;
import org.apache.james.modules.blobstore.BlobStoreConfiguration;
@@ -58,6 +59,7 @@ public class PostgresPushSubscriptionSetMethodTest implements
PushSubscriptionSe
.server(configuration ->
PostgresJamesServerMain.createServer(configuration)
.overrideWith(new TestJMAPServerModule())
.overrideWith(new PushSubscriptionProbeModule())
+ .overrideWith(new TypeStateModule())
.overrideWith(binder ->
binder.bind(PushClientConfiguration.class).toInstance(PushClientConfiguration.UNSAFE_DEFAULT())))
.build();
diff --git
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/PushSubscriptionSet.scala
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/PushSubscriptionSet.scala
index 9125567f01..6b1b5b07a9 100644
---
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/PushSubscriptionSet.scala
+++
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/PushSubscriptionSet.scala
@@ -35,7 +35,7 @@ import org.apache.james.jmap.core.Properties.toProperties
import org.apache.james.jmap.core.SetError.SetErrorDescription
import org.apache.james.jmap.mail.{InvalidPropertyException,
InvalidUpdateException, PatchUpdateValidationException,
UnsupportedPropertyUpdatedException}
import org.apache.james.jmap.method.{SetRequest, WithoutAccountId}
-import play.api.libs.json.{JsArray, JsObject, JsString, JsValue}
+import play.api.libs.json.{JsArray, JsNull, JsObject, JsString, JsValue}
import scala.util.{Failure, Success, Try}
@@ -133,7 +133,8 @@ object TypesUpdate {
.map(js => parseType(js, typeStateFactory))
.sequence
.map(_.toSet)
- .map(TypesUpdate(_))
+ .map(types => TypesUpdate(Some(types)))
+ case JsNull => Right(TypesUpdate(None))
case _ => Left(InvalidUpdateException("types", "Expecting an array of JSON
strings as an argument"))
}
def parseType(jsValue: JsValue, typeStateFactory: TypeStateFactory):
Either[PatchUpdateValidationException, TypeName] = jsValue match {
@@ -156,7 +157,7 @@ object ExpiresUpdate {
sealed trait Update
case class VerificationCodeUpdate(newVerificationCode: VerificationCode)
extends Update
-case class TypesUpdate(types: Set[TypeName]) extends Update
+case class TypesUpdate(types: Option[Set[TypeName]]) extends Update
case class ExpiresUpdate(newExpires: UTCDate) extends Update
object ValidatedPushSubscriptionPatchObject {
@@ -166,7 +167,7 @@ object ValidatedPushSubscriptionPatchObject {
}
case class ValidatedPushSubscriptionPatchObject(verificationCodeUpdate:
Option[VerificationCode],
- typesUpdate:
Option[Set[TypeName]],
+ typesUpdate:
Option[Option[Set[TypeName]]],
expiresUpdate:
Option[PushSubscriptionExpiredTime]) {
val shouldUpdate: Boolean = verificationCodeUpdate.isDefined ||
typesUpdate.isDefined || expiresUpdate.isDefined
diff --git
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/PushSubscriptionUpdatePerformer.scala
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/PushSubscriptionUpdatePerformer.scala
index 22a65d5f91..9dd11d25f4 100644
---
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/PushSubscriptionUpdatePerformer.scala
+++
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/PushSubscriptionUpdatePerformer.scala
@@ -130,9 +130,12 @@ class PushSubscriptionUpdatePerformer
@Inject()(pushSubscriptionRepository: Push
SMono.error[PushSubscriptionUpdateResult](WrongVerificationCodeException())
}
- private def updateTypes(pushSubscription: PushSubscription, types:
Set[TypeName], mailboxSession: MailboxSession):
SMono[PushSubscriptionUpdateResult] =
+ private def updateTypes(pushSubscription: PushSubscription, maybeTypes:
Option[Set[TypeName]], mailboxSession: MailboxSession):
SMono[PushSubscriptionUpdateResult] = {
+ val types: Set[TypeName] = maybeTypes.getOrElse(typeStateFactory.all)
+
SMono(pushSubscriptionRepository.updateTypes(mailboxSession.getUser,
pushSubscription.id, types.asJava))
.`then`(SMono.just(PushSubscriptionUpdateSuccess(pushSubscription.id)))
+ }
private def updateExpires(pushSubscription: PushSubscription, inputExpires:
PushSubscriptionExpiredTime, mailboxSession: MailboxSession):
SMono[PushSubscriptionUpdateResult] =
SMono(pushSubscriptionRepository.updateExpireTime(mailboxSession.getUser,
pushSubscription.id, inputExpires.value))
diff --git
a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/pushsubscription/PushListenerTest.scala
b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/pushsubscription/PushListenerTest.scala
index 53ef0a4c7a..d1c4f3b9e3 100644
---
a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/pushsubscription/PushListenerTest.scala
+++
b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/pushsubscription/PushListenerTest.scala
@@ -66,7 +66,8 @@ class PushListenerTest {
def setUp(): Unit = {
val pushSerializer =
PushSerializer(TypeStateFactory(ImmutableSet.of[TypeName](MailboxTypeName,
EmailTypeName, EmailDeliveryTypeName)))
- pushSubscriptionRepository = new
MemoryPushSubscriptionRepository(Clock.systemUTC())
+ pushSubscriptionRepository = new
MemoryPushSubscriptionRepository(Clock.systemUTC(),
+ TypeStateFactory(ImmutableSet.of[TypeName](MailboxTypeName,
EmailTypeName, EmailDeliveryTypeName)))
webPushClient = mock(classOf[WebPushClient])
delegationStore = new MemoryDelegationStore()
testee = new PushListener(pushSubscriptionRepository, webPushClient,
pushSerializer, delegationStore, Clock.systemUTC())
@@ -87,7 +88,7 @@ class PushListenerTest {
SMono(pushSubscriptionRepository.save(bob, PushSubscriptionCreationRequest(
deviceClientId = DeviceClientId("junit"),
url = url,
- types = Seq(MailboxTypeName, EmailTypeName)))).block()
+ types = Some(Seq(MailboxTypeName, EmailTypeName))))).block()
SMono(testee.reactiveEvent(StateChangeEvent(EventId.random(), bob,
Map(EmailTypeName -> UuidState(UUID.randomUUID()))))).block()
@@ -100,7 +101,7 @@ class PushListenerTest {
val id = SMono(pushSubscriptionRepository.save(bob,
PushSubscriptionCreationRequest(
deviceClientId = DeviceClientId("junit"),
url = url,
- types = Seq(EmailDeliveryTypeName)))).block().id
+ types = Some(Seq(EmailDeliveryTypeName))))).block().id
SMono(pushSubscriptionRepository.validateVerificationCode(bob, id)).block()
SMono(testee.reactiveEvent(StateChangeEvent(EventId.random(), bob,
@@ -114,7 +115,7 @@ class PushListenerTest {
val id = SMono(pushSubscriptionRepository.save(bob,
PushSubscriptionCreationRequest(
deviceClientId = DeviceClientId("junit"),
url = url,
- types = Seq(EmailTypeName, MailboxTypeName)))).block().id
+ types = Some(Seq(EmailTypeName, MailboxTypeName))))).block().id
SMono(pushSubscriptionRepository.validateVerificationCode(bob, id)).block()
val state1 = UuidState(UUID.randomUUID())
@@ -136,7 +137,7 @@ class PushListenerTest {
val bobSubscriptionId = SMono(pushSubscriptionRepository.save(bob,
PushSubscriptionCreationRequest(
deviceClientId = DeviceClientId("junit1"),
url = url,
- types = Seq(EmailTypeName, MailboxTypeName)))).block().id
+ types = Some(Seq(EmailTypeName, MailboxTypeName))))).block().id
SMono(pushSubscriptionRepository.validateVerificationCode(bob,
bobSubscriptionId)).block()
val state1 = UuidState(UUID.randomUUID())
@@ -152,12 +153,12 @@ class PushListenerTest {
val bobSubscriptionId = SMono(pushSubscriptionRepository.save(bob,
PushSubscriptionCreationRequest(
deviceClientId = DeviceClientId("junit1"),
url = url,
- types = Seq(EmailTypeName, MailboxTypeName)))).block().id
+ types = Some(Seq(EmailTypeName, MailboxTypeName))))).block().id
SMono(pushSubscriptionRepository.validateVerificationCode(bob,
bobSubscriptionId)).block()
val aliceSubscriptionId = SMono(pushSubscriptionRepository.save(alice,
PushSubscriptionCreationRequest(
deviceClientId = DeviceClientId("junit2"),
url = url,
- types = Seq(EmailTypeName, MailboxTypeName)))).block().id
+ types = Some(Seq(EmailTypeName, MailboxTypeName))))).block().id
SMono(pushSubscriptionRepository.validateVerificationCode(alice,
aliceSubscriptionId)).block()
val state1 = UuidState(UUID.randomUUID())
@@ -180,7 +181,7 @@ class PushListenerTest {
val bobSubscriptionId = SMono(pushSubscriptionRepository.save(bob,
PushSubscriptionCreationRequest(
deviceClientId = DeviceClientId("junit1"),
url = url,
- types = Seq(EmailTypeName, MailboxTypeName)))).block().id
+ types = Some(Seq(EmailTypeName, MailboxTypeName))))).block().id
SMono(pushSubscriptionRepository.validateVerificationCode(bob,
bobSubscriptionId)).block()
val stateChangeBob = UuidState(UUID.randomUUID())
@@ -203,7 +204,7 @@ class PushListenerTest {
val id = SMono(pushSubscriptionRepository.save(bob,
PushSubscriptionCreationRequest(
deviceClientId = DeviceClientId("junit"),
url = url,
- types = Seq(EmailTypeName, MailboxTypeName)))).block().id
+ types = Some(Seq(EmailTypeName, MailboxTypeName))))).block().id
SMono(pushSubscriptionRepository.validateVerificationCode(bob, id)).block()
val state1 = UuidState(UUID.randomUUID())
@@ -227,7 +228,7 @@ class PushListenerTest {
val id = SMono(pushSubscriptionRepository.save(bob,
PushSubscriptionCreationRequest(
deviceClientId = DeviceClientId("junit"),
url = url,
- types = Seq(EmailDeliveryTypeName, EmailTypeName)))).block().id
+ types = Some(Seq(EmailDeliveryTypeName, EmailTypeName))))).block().id
SMono(pushSubscriptionRepository.validateVerificationCode(bob, id)).block()
val state1 = UuidState(UUID.randomUUID())
@@ -246,7 +247,7 @@ class PushListenerTest {
val id = SMono(pushSubscriptionRepository.save(bob,
PushSubscriptionCreationRequest(
deviceClientId = DeviceClientId("junit"),
url = url,
- types = Seq(EmailDeliveryTypeName, EmailTypeName)))).block().id
+ types = Some(Seq(EmailDeliveryTypeName, EmailTypeName))))).block().id
SMono(pushSubscriptionRepository.validateVerificationCode(bob, id)).block()
val state1 = UuidState(UUID.randomUUID())
@@ -264,7 +265,7 @@ class PushListenerTest {
val id = SMono(pushSubscriptionRepository.save(bob,
PushSubscriptionCreationRequest(
deviceClientId = DeviceClientId("junit"),
url = url,
- types = Seq(EmailDeliveryTypeName, EmailTypeName)))).block().id
+ types = Some(Seq(EmailDeliveryTypeName, EmailTypeName))))).block().id
SMono(pushSubscriptionRepository.validateVerificationCode(bob, id)).block()
val state1 = UuidState(UUID.randomUUID())
@@ -296,7 +297,7 @@ class PushListenerTest {
keys = Some(PushSubscriptionKeys(p256dh =
Base64.getUrlEncoder.encodeToString(uaPublicKey.getEncoded),
auth = Base64.getUrlEncoder.encodeToString(authSecret))),
url = url,
- types = Seq(EmailTypeName, MailboxTypeName)))).block().id
+ types = Some(Seq(EmailTypeName, MailboxTypeName))))).block().id
SMono(pushSubscriptionRepository.validateVerificationCode(bob, id)).block()
val state1 = UuidState(UUID.randomUUID())
diff --git
a/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/memory/MemoryUserDeletionIntegrationTest.java
b/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/memory/MemoryUserDeletionIntegrationTest.java
index a421047f75..b6dc3e3a66 100644
---
a/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/memory/MemoryUserDeletionIntegrationTest.java
+++
b/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/memory/MemoryUserDeletionIntegrationTest.java
@@ -291,7 +291,7 @@ class MemoryUserDeletionIntegrationTest {
new PushSubscriptionServerURL(new
URI("http://whatever/toto").toURL()),
Option.empty(),
Option.empty(),
- PushSubscriptionCreationRequest.noTypes()));
+ Option.apply(PushSubscriptionCreationRequest.noTypes())));
String taskId = webAdminApi
.queryParam("action", "deleteData")
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]