You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2020/12/21 07:29:39 UTC
[james-project] 14/17: JAMES-3481 Mailbox/change handle delegated
mailbox
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
commit 833710a5056cfc980452fee7ebc0c92a1d16451b
Author: LanKhuat <dl...@linagora.com>
AuthorDate: Tue Dec 15 18:14:05 2020 +0700
JAMES-3481 Mailbox/change handle delegated mailbox
---
.../james/jmap/api/change/MailboxChange.java | 175 ++++-
.../jmap/api/change/MailboxChangeRepository.java | 2 +-
.../change/MailboxChangeRepositoryContract.java | 136 ++--
.../contract/MailboxChangesMethodContract.scala | 718 +++++++++++++++++++--
.../james/jmap/change/MailboxChangeListener.scala | 22 +-
.../jmap/change/MailboxChangeListenerTest.scala | 26 +-
6 files changed, 964 insertions(+), 115 deletions(-)
diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChange.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChange.java
index 52461e6..5613db9 100644
--- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChange.java
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChange.java
@@ -25,10 +25,14 @@ import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
+import java.util.stream.Stream;
import javax.mail.Flags;
+import org.apache.james.core.Username;
import org.apache.james.jmap.api.model.AccountId;
+import org.apache.james.mailbox.MailboxManager;
+import org.apache.james.mailbox.MailboxSession;
import org.apache.james.mailbox.events.Event;
import org.apache.james.mailbox.events.MailboxListener.Added;
import org.apache.james.mailbox.events.MailboxListener.Expunged;
@@ -37,8 +41,12 @@ import org.apache.james.mailbox.events.MailboxListener.MailboxACLUpdated;
import org.apache.james.mailbox.events.MailboxListener.MailboxAdded;
import org.apache.james.mailbox.events.MailboxListener.MailboxDeletion;
import org.apache.james.mailbox.events.MailboxListener.MailboxRenamed;
+import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.mailbox.model.MailboxACL;
import org.apache.james.mailbox.model.MailboxId;
+import org.apache.james.mailbox.model.MailboxPath;
+import com.github.steveash.guavate.Guavate;
import com.google.common.collect.ImmutableList;
public class MailboxChange {
@@ -93,31 +101,139 @@ public class MailboxChange {
}
}
- public static MailboxChange of(AccountId accountId, State state, ZonedDateTime date, List<MailboxId> created, List<MailboxId> updated, List<MailboxId> destroyed) {
- return new MailboxChange(accountId, state, date, created, updated, destroyed);
+ @FunctionalInterface
+ interface RequiredAccountId {
+ RequiredState accountId(AccountId accountId);
}
- public static Optional<MailboxChange> fromEvent(Event event) {
+ @FunctionalInterface
+ interface RequiredState {
+ RequiredDate state(State state);
+ }
+
+ @FunctionalInterface
+ interface RequiredDate {
+ Builder date(ZonedDateTime date);
+ }
+
+ public static class Builder {
+ private final AccountId accountId;
+ private final State state;
+ private final ZonedDateTime date;
+ private boolean delegated;
+ private Optional<List<MailboxId>> created;
+ private Optional<List<MailboxId>> updated;
+ private Optional<List<MailboxId>> destroyed;
+
+ private Builder(AccountId accountId, State state, ZonedDateTime date) {
+ this.accountId = accountId;
+ this.state = state;
+ this.date = date;
+ this.created = Optional.empty();
+ this.updated = Optional.empty();
+ this.destroyed = Optional.empty();
+ }
+
+ public Builder delegated() {
+ this.delegated = true;
+ return this;
+ }
+
+ public Builder created(List<MailboxId> created) {
+ this.created = Optional.of(created);
+ return this;
+ }
+
+ public Builder updated(List<MailboxId> updated) {
+ this.updated = Optional.of(updated);
+ return this;
+ }
+
+ public Builder destroyed(List<MailboxId> destroyed) {
+ this.destroyed = Optional.of(destroyed);
+ return this;
+ }
+
+ public MailboxChange build() {
+ return new MailboxChange(accountId, state, date, delegated, created.orElse(ImmutableList.of()), updated.orElse(ImmutableList.of()), destroyed.orElse(ImmutableList.of()));
+ }
+ }
+
+ public static RequiredAccountId builder() {
+ return accountId -> state -> date -> new Builder(accountId, state, date);
+ }
+
+ public static Builder created(AccountId accountId, State state, ZonedDateTime date, List<MailboxId> created) {
+ return MailboxChange.builder()
+ .accountId(accountId)
+ .state(state)
+ .date(date)
+ .created(created);
+ }
+
+ public static Builder updated(AccountId accountId, State state, ZonedDateTime date, List<MailboxId> updated) {
+ return MailboxChange.builder()
+ .accountId(accountId)
+ .state(state)
+ .date(date)
+ .updated(updated);
+ }
+
+ public static Builder destroyed(AccountId accountId, State state, ZonedDateTime date, List<MailboxId> destroyed) {
+ return MailboxChange.builder()
+ .accountId(accountId)
+ .state(state)
+ .date(date)
+ .destroyed(destroyed);
+ }
+
+ public static Optional<List<MailboxChange>> fromEvent(Event event, MailboxManager mailboxManager) {
ZonedDateTime now = ZonedDateTime.now(Clock.systemUTC());
if (event instanceof MailboxAdded) {
MailboxAdded mailboxAdded = (MailboxAdded) event;
- return Optional.of(MailboxChange.of(AccountId.fromUsername(mailboxAdded.getUsername()), State.of(UUID.randomUUID()), now, ImmutableList.of(mailboxAdded.getMailboxId()), ImmutableList.of(), ImmutableList.of()));
+ return Optional.of(ImmutableList.of(MailboxChange.created(AccountId.fromUsername(mailboxAdded.getUsername()), State.of(UUID.randomUUID()), now, ImmutableList.of(mailboxAdded.getMailboxId())).build()));
}
if (event instanceof MailboxRenamed) {
MailboxRenamed mailboxRenamed = (MailboxRenamed) event;
- return Optional.of(MailboxChange.of(AccountId.fromUsername(mailboxRenamed.getUsername()), State.of(UUID.randomUUID()), now, ImmutableList.of(), ImmutableList.of(mailboxRenamed.getMailboxId()), ImmutableList.of()));
+
+ MailboxChange ownerChange = MailboxChange.updated(AccountId.fromUsername(mailboxRenamed.getUsername()), State.of(UUID.randomUUID()), now, ImmutableList.of(mailboxRenamed.getMailboxId())).build();
+ Stream<MailboxChange> shareeChanges = getSharees(mailboxRenamed.getNewPath(), mailboxRenamed.getUsername(), mailboxManager)
+ .map(name -> MailboxChange.updated(AccountId.fromString(name), State.of(UUID.randomUUID()), now, ImmutableList.of(mailboxRenamed.getMailboxId()))
+ .delegated()
+ .build());
+
+ List<MailboxChange> blah = Stream.concat(Stream.of(ownerChange), shareeChanges).collect(Guavate.toImmutableList());
+ return Optional.of(blah);
}
if (event instanceof MailboxACLUpdated) {
MailboxACLUpdated mailboxACLUpdated = (MailboxACLUpdated) event;
- return Optional.of(MailboxChange.of(AccountId.fromUsername(mailboxACLUpdated.getUsername()), State.of(UUID.randomUUID()), now, ImmutableList.of(), ImmutableList.of(mailboxACLUpdated.getMailboxId()), ImmutableList.of()));
+
+ MailboxChange ownerChange = MailboxChange.updated(AccountId.fromUsername(mailboxACLUpdated.getUsername()), State.of(UUID.randomUUID()), now, ImmutableList.of(mailboxACLUpdated.getMailboxId())).build();
+ Stream<MailboxChange> shareeChanges = getSharees(mailboxACLUpdated.getMailboxPath(), mailboxACLUpdated.getUsername(), mailboxManager)
+ .map(name -> MailboxChange.updated(AccountId.fromString(name), State.of(UUID.randomUUID()), now, ImmutableList.of(mailboxACLUpdated.getMailboxId()))
+ .delegated()
+ .build());
+
+ return Optional.of(
+ Stream.concat(Stream.of(ownerChange), shareeChanges)
+ .collect(Guavate.toImmutableList()));
}
if (event instanceof MailboxDeletion) {
MailboxDeletion mailboxDeletion = (MailboxDeletion) event;
- return Optional.of(MailboxChange.of(AccountId.fromUsername(mailboxDeletion.getUsername()), State.of(UUID.randomUUID()), now, ImmutableList.of(), ImmutableList.of(), ImmutableList.of(mailboxDeletion.getMailboxId())));
+ return Optional.of(ImmutableList.of(MailboxChange.destroyed(AccountId.fromUsername(mailboxDeletion.getUsername()), State.of(UUID.randomUUID()), now, ImmutableList.of(mailboxDeletion.getMailboxId())).build()));
}
if (event instanceof Added) {
Added messageAdded = (Added) event;
- return Optional.of(MailboxChange.of(AccountId.fromUsername(messageAdded.getUsername()), State.of(UUID.randomUUID()), now, ImmutableList.of(), ImmutableList.of(messageAdded.getMailboxId()), ImmutableList.of()));
+
+ MailboxChange ownerChange = MailboxChange.updated(AccountId.fromUsername(messageAdded.getUsername()), State.of(UUID.randomUUID()), now, ImmutableList.of(messageAdded.getMailboxId())).build();
+ Stream<MailboxChange> shareeChanges = getSharees(messageAdded.getMailboxPath(), messageAdded.getUsername(), mailboxManager)
+ .map(name -> MailboxChange.updated(AccountId.fromString(name), State.of(UUID.randomUUID()), now, ImmutableList.of(messageAdded.getMailboxId()))
+ .delegated()
+ .build());
+
+ return Optional.of(
+ Stream.concat(Stream.of(ownerChange), shareeChanges)
+ .collect(Guavate.toImmutableList()));
}
if (event instanceof FlagsUpdated) {
FlagsUpdated messageFlagUpdated = (FlagsUpdated) event;
@@ -125,28 +241,61 @@ public class MailboxChange {
.stream()
.anyMatch(flags -> flags.isChanged(Flags.Flag.SEEN));
if (isSeenChanged) {
- return Optional.of(MailboxChange.of(AccountId.fromUsername(messageFlagUpdated.getUsername()), State.of(UUID.randomUUID()), now, ImmutableList.of(), ImmutableList.of(messageFlagUpdated.getMailboxId()), ImmutableList.of()));
+
+ MailboxChange ownerChange = MailboxChange.updated(AccountId.fromUsername(messageFlagUpdated.getUsername()), State.of(UUID.randomUUID()), now, ImmutableList.of(messageFlagUpdated.getMailboxId())).build();
+ Stream<MailboxChange> shareeChanges = getSharees(messageFlagUpdated.getMailboxPath(), messageFlagUpdated.getUsername(), mailboxManager)
+ .map(name -> MailboxChange.updated(AccountId.fromString(name), State.of(UUID.randomUUID()), now, ImmutableList.of(messageFlagUpdated.getMailboxId()))
+ .delegated()
+ .build());
+
+ return Optional.of(
+ Stream.concat(Stream.of(ownerChange), shareeChanges)
+ .collect(Guavate.toImmutableList()));
}
}
if (event instanceof Expunged) {
Expunged expunged = (Expunged) event;
- return Optional.of(MailboxChange.of(AccountId.fromUsername(expunged.getUsername()), State.of(UUID.randomUUID()), now, ImmutableList.of(), ImmutableList.of(expunged.getMailboxId()), ImmutableList.of()));
+
+ MailboxChange ownerChange = MailboxChange.updated(AccountId.fromUsername(expunged.getUsername()), State.of(UUID.randomUUID()), now, ImmutableList.of(expunged.getMailboxId())).build();
+ Stream<MailboxChange> shareeChanges = getSharees(expunged.getMailboxPath(), expunged.getUsername(), mailboxManager)
+ .map(name -> MailboxChange.updated(AccountId.fromString(name), State.of(UUID.randomUUID()), now, ImmutableList.of(expunged.getMailboxId()))
+ .delegated()
+ .build());
+
+ return Optional.of(
+ Stream.concat(Stream.of(ownerChange), shareeChanges)
+ .collect(Guavate.toImmutableList()));
}
return Optional.empty();
}
+ private static Stream<String> getSharees(MailboxPath path, Username username, MailboxManager mailboxManager) {
+ MailboxSession mailboxSession = mailboxManager.createSystemSession(username);
+ try {
+ MailboxACL mailboxACL = mailboxManager.listRights(path, mailboxSession);
+ return mailboxACL.getEntries().keySet()
+ .stream()
+ .filter(rfc4314Rights -> !rfc4314Rights.isNegative())
+ .map(MailboxACL.EntryKey::getName);
+ } catch (MailboxException e) {
+ return Stream.of();
+ }
+ }
+
private final AccountId accountId;
private final State state;
private final ZonedDateTime date;
+ private final boolean delegated;
private final List<MailboxId> created;
private final List<MailboxId> updated;
private final List<MailboxId> destroyed;
- private MailboxChange(AccountId accountId, State state, ZonedDateTime date, List<MailboxId> created, List<MailboxId> updated, List<MailboxId> destroyed) {
+ private MailboxChange(AccountId accountId, State state, ZonedDateTime date, boolean delegated, List<MailboxId> created, List<MailboxId> updated, List<MailboxId> destroyed) {
this.accountId = accountId;
this.state = state;
this.date = date;
+ this.delegated = delegated;
this.created = created;
this.updated = updated;
this.destroyed = destroyed;
@@ -164,6 +313,10 @@ public class MailboxChange {
return date;
}
+ public boolean isDelegated() {
+ return delegated;
+ }
+
public List<MailboxId> getCreated() {
return created;
}
diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChangeRepository.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChangeRepository.java
index ccc0a36..006316b 100644
--- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChangeRepository.java
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChangeRepository.java
@@ -31,7 +31,7 @@ public interface MailboxChangeRepository {
Mono<Void> save(MailboxChange change);
- Mono<MailboxChanges> getSinceState(AccountId accountId, State state, Optional<Limit> maxIdsToReturn);
+ Mono<MailboxChanges> getSinceState(AccountId accountId, State state, Optional<Limit> maxChanges);
Mono<State> getLatestState(AccountId accountId);
}
diff --git a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/change/MailboxChangeRepositoryContract.java b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/change/MailboxChangeRepositoryContract.java
index 9cb83475..26c04ba 100644
--- a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/change/MailboxChangeRepositoryContract.java
+++ b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/change/MailboxChangeRepositoryContract.java
@@ -49,7 +49,7 @@ public interface MailboxChangeRepositoryContract {
default void saveChangeShouldSuccess() {
MailboxChangeRepository repository = mailboxChangeRepository();
- MailboxChange change = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE, ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
+ MailboxChange change = MailboxChange.created(ACCOUNT_ID, STATE_0, DATE, ImmutableList.of(TestId.of(1))).build();
assertThatCode(() -> repository.save(change).block())
.doesNotThrowAnyException();
@@ -67,9 +67,9 @@ public interface MailboxChangeRepositoryContract {
default void getLatestStateShouldReturnLastPersistedState() {
MailboxChangeRepository repository = mailboxChangeRepository();
- MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(2), ImmutableList.of(TestId.of(2)), ImmutableList.of(), ImmutableList.of());
- MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(3)), ImmutableList.of(), ImmutableList.of());
- MailboxChange change3 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(TestId.of(4)), ImmutableList.of(), ImmutableList.of());
+ MailboxChange change1 = MailboxChange.builder().accountId(ACCOUNT_ID).state(State.of(UUID.randomUUID())).date(DATE.minusHours(2)).created(ImmutableList.of(TestId.of(2))).build();
+ MailboxChange change2 = MailboxChange.builder().accountId(ACCOUNT_ID).state(State.of(UUID.randomUUID())).date(DATE.minusHours(1)).created(ImmutableList.of(TestId.of(3))).build();
+ MailboxChange change3 = MailboxChange.builder().accountId(ACCOUNT_ID).state(State.of(UUID.randomUUID())).date(DATE).created(ImmutableList.of(TestId.of(4))).build();
repository.save(change1).block();
repository.save(change2).block();
repository.save(change3).block();
@@ -82,7 +82,7 @@ public interface MailboxChangeRepositoryContract {
default void saveChangeShouldFailWhenNoAccountId() {
MailboxChangeRepository repository = mailboxChangeRepository();
- MailboxChange change = MailboxChange.of(null, STATE_0, DATE, ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
+ MailboxChange change = MailboxChange.created(null, STATE_0, DATE, ImmutableList.of(TestId.of(1))).build();
assertThatThrownBy(() -> repository.save(change).block())
.isInstanceOf(NullPointerException.class);
@@ -92,7 +92,7 @@ public interface MailboxChangeRepositoryContract {
default void saveChangeShouldFailWhenNoState() {
MailboxChangeRepository repository = mailboxChangeRepository();
- MailboxChange change = MailboxChange.of(ACCOUNT_ID, null, DATE, ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
+ MailboxChange change = MailboxChange.created(ACCOUNT_ID, null, DATE, ImmutableList.of(TestId.of(1))).build();
assertThatThrownBy(() -> repository.save(change).block())
.isInstanceOf(NullPointerException.class);
@@ -102,8 +102,8 @@ public interface MailboxChangeRepositoryContract {
default void getChangesShouldSuccess() {
MailboxChangeRepository repository = mailboxChangeRepository();
- MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE.minusHours(1), ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
- MailboxChange change = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(), ImmutableList.of(TestId.of(1)), ImmutableList.of());
+ MailboxChange oldState = MailboxChange.builder().accountId(ACCOUNT_ID).state(STATE_0).date(DATE.minusHours(1)).created(ImmutableList.of(TestId.of(1))).build();
+ MailboxChange change = MailboxChange.builder().accountId(ACCOUNT_ID).state(State.of(UUID.randomUUID())).date(DATE).updated(ImmutableList.of(TestId.of(1))).build();
repository.save(oldState).block();
repository.save(change).block();
@@ -115,7 +115,7 @@ public interface MailboxChangeRepositoryContract {
default void getChangesShouldReturnEmptyWhenNoNewerState() {
MailboxChangeRepository repository = mailboxChangeRepository();
- MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE, ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
+ MailboxChange oldState = MailboxChange.builder().accountId(ACCOUNT_ID).state(STATE_0).date(DATE).created(ImmutableList.of(TestId.of(1))).build();
repository.save(oldState).block();
assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.empty()).block().getAllChanges())
@@ -126,7 +126,7 @@ public interface MailboxChangeRepositoryContract {
default void getChangesShouldReturnCurrentStateWhenNoNewerState() {
MailboxChangeRepository repository = mailboxChangeRepository();
- MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE, ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
+ MailboxChange oldState = MailboxChange.builder().accountId(ACCOUNT_ID).state(STATE_0).date(DATE).created(ImmutableList.of(TestId.of(1))).build();
repository.save(oldState).block();
assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.empty()).block().getNewState())
@@ -137,10 +137,10 @@ public interface MailboxChangeRepositoryContract {
default void getChangesShouldLimitChanges() {
MailboxChangeRepository repository = mailboxChangeRepository();
- MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE.minusHours(3), ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
- MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(2), ImmutableList.of(TestId.of(2)), ImmutableList.of(), ImmutableList.of());
- MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(3)), ImmutableList.of(), ImmutableList.of());
- MailboxChange change3 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(TestId.of(4)), ImmutableList.of(), ImmutableList.of());
+ MailboxChange oldState = MailboxChange.builder().accountId(ACCOUNT_ID).state(STATE_0).date(DATE.minusHours(3)).created(ImmutableList.of(TestId.of(1))).build();
+ MailboxChange change1 = MailboxChange.builder().accountId(ACCOUNT_ID).state(State.of(UUID.randomUUID())).date(DATE.minusHours(2)).created(ImmutableList.of(TestId.of(2))).build();
+ MailboxChange change2 = MailboxChange.builder().accountId(ACCOUNT_ID).state(State.of(UUID.randomUUID())).date(DATE.minusHours(1)).created(ImmutableList.of(TestId.of(3))).build();
+ MailboxChange change3 = MailboxChange.builder().accountId(ACCOUNT_ID).state(State.of(UUID.randomUUID())).date(DATE).created(ImmutableList.of(TestId.of(4))).build();
repository.save(oldState).block();
repository.save(change1).block();
repository.save(change2).block();
@@ -154,10 +154,10 @@ public interface MailboxChangeRepositoryContract {
default void getChangesShouldReturnAllFromInitial() {
MailboxChangeRepository repository = mailboxChangeRepository();
- MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE.minusHours(3), ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
- MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(2), ImmutableList.of(TestId.of(2)), ImmutableList.of(), ImmutableList.of());
- MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(3)), ImmutableList.of(), ImmutableList.of());
- MailboxChange change3 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(TestId.of(4)), ImmutableList.of(), ImmutableList.of());
+ MailboxChange oldState = MailboxChange.builder().accountId(ACCOUNT_ID).state(STATE_0).date(DATE.minusHours(3)).created(ImmutableList.of(TestId.of(1))).build();
+ MailboxChange change1 = MailboxChange.builder().accountId(ACCOUNT_ID).state(State.of(UUID.randomUUID())).date(DATE.minusHours(2)).created(ImmutableList.of(TestId.of(2))).build();
+ MailboxChange change2 = MailboxChange.builder().accountId(ACCOUNT_ID).state(State.of(UUID.randomUUID())).date(DATE.minusHours(1)).created(ImmutableList.of(TestId.of(3))).build();
+ MailboxChange change3 = MailboxChange.builder().accountId(ACCOUNT_ID).state(State.of(UUID.randomUUID())).date(DATE).created(ImmutableList.of(TestId.of(4))).build();
repository.save(oldState).block();
repository.save(change1).block();
repository.save(change2).block();
@@ -171,11 +171,11 @@ public interface MailboxChangeRepositoryContract {
default void getChangesFromInitialShouldReturnNewState() {
MailboxChangeRepository repository = mailboxChangeRepository();
- MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE.minusHours(3), ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
- MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(2), ImmutableList.of(TestId.of(2)), ImmutableList.of(), ImmutableList.of());
+ MailboxChange oldState = MailboxChange.builder().accountId(ACCOUNT_ID).state(STATE_0).date(DATE.minusHours(3)).created(ImmutableList.of(TestId.of(1))).build();
+ MailboxChange change1 = MailboxChange.builder().accountId(ACCOUNT_ID).state(State.of(UUID.randomUUID())).date(DATE.minusHours(2)).created(ImmutableList.of(TestId.of(2))).build();
State state2 = State.of(UUID.randomUUID());
- MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, state2, DATE.minusHours(1), ImmutableList.of(TestId.of(3)), ImmutableList.of(), ImmutableList.of());
- MailboxChange change3 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(TestId.of(4)), ImmutableList.of(), ImmutableList.of());
+ MailboxChange change2 = MailboxChange.builder().accountId(ACCOUNT_ID).state(state2).date(DATE.minusHours(1)).created(ImmutableList.of(TestId.of(3))).build();
+ MailboxChange change3 = MailboxChange.builder().accountId(ACCOUNT_ID).state(State.of(UUID.randomUUID())).date(DATE).created(ImmutableList.of(TestId.of(4))).build();
repository.save(oldState).block();
repository.save(change1).block();
repository.save(change2).block();
@@ -190,9 +190,9 @@ public interface MailboxChangeRepositoryContract {
default void getChangesShouldLimitChangesWhenMaxChangesOmitted() {
MailboxChangeRepository repository = mailboxChangeRepository();
- MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE.minusHours(2), ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
- MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(2), TestId.of(3), TestId.of(4), TestId.of(5), TestId.of(6)), ImmutableList.of(), ImmutableList.of());
- MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(TestId.of(7)), ImmutableList.of(), ImmutableList.of());
+ MailboxChange oldState = MailboxChange.created(ACCOUNT_ID, STATE_0, DATE.minusHours(2), ImmutableList.of(TestId.of(1))).build();
+ MailboxChange change1 = MailboxChange.created(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(2), TestId.of(3), TestId.of(4), TestId.of(5), TestId.of(6))).build();
+ MailboxChange change2 = MailboxChange.created(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(TestId.of(7))).build();
repository.save(oldState).block();
repository.save(change1).block();
@@ -206,9 +206,9 @@ public interface MailboxChangeRepositoryContract {
default void getChangesShouldNotReturnMoreThanMaxChanges() {
MailboxChangeRepository repository = mailboxChangeRepository();
- MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE.minusHours(2), ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
- MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(2), TestId.of(3)), ImmutableList.of(), ImmutableList.of());
- MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(TestId.of(4), TestId.of(5)), ImmutableList.of(), ImmutableList.of());
+ MailboxChange oldState = MailboxChange.builder().accountId(ACCOUNT_ID).state(STATE_0).date(DATE.minusHours(2)).created(ImmutableList.of(TestId.of(1))).build();
+ MailboxChange change1 = MailboxChange.builder().accountId(ACCOUNT_ID).state(State.of(UUID.randomUUID())).date(DATE.minusHours(1)).created(ImmutableList.of(TestId.of(2), TestId.of(3))).build();
+ MailboxChange change2 = MailboxChange.builder().accountId(ACCOUNT_ID).state(State.of(UUID.randomUUID())).date(DATE).created(ImmutableList.of(TestId.of(4), TestId.of(5))).build();
repository.save(oldState).block();
repository.save(change1).block();
repository.save(change2).block();
@@ -221,8 +221,8 @@ public interface MailboxChangeRepositoryContract {
default void getChangesShouldReturnEmptyWhenNumberOfChangesExceedMaxChanges() {
MailboxChangeRepository repository = mailboxChangeRepository();
- MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE.minusHours(2), ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
- MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(2), TestId.of(3)), ImmutableList.of(), ImmutableList.of());
+ MailboxChange oldState = MailboxChange.builder().accountId(ACCOUNT_ID).state(STATE_0).date(DATE.minusHours(2)).created(ImmutableList.of(TestId.of(1))).build();
+ MailboxChange change1 = MailboxChange.builder().accountId(ACCOUNT_ID).state(State.of(UUID.randomUUID())).date(DATE.minusHours(1)).created(ImmutableList.of(TestId.of(2), TestId.of(3))).build();
repository.save(oldState).block();
repository.save(change1).block();
@@ -234,9 +234,9 @@ public interface MailboxChangeRepositoryContract {
default void getChangesShouldReturnNewState() {
MailboxChangeRepository repository = mailboxChangeRepository();
- MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE.minusHours(2), ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
- MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(2), TestId.of(3)), ImmutableList.of(), ImmutableList.of());
- MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(), ImmutableList.of(TestId.of(2), TestId.of(3)), ImmutableList.of());
+ MailboxChange oldState = MailboxChange.builder().accountId(ACCOUNT_ID).state(STATE_0).date(DATE.minusHours(2)).created(ImmutableList.of(TestId.of(1))).build();
+ MailboxChange change1 = MailboxChange.builder().accountId(ACCOUNT_ID).state(State.of(UUID.randomUUID())).date(DATE.minusHours(1)).created(ImmutableList.of(TestId.of(2), TestId.of(3))).build();
+ MailboxChange change2 = MailboxChange.builder().accountId(ACCOUNT_ID).state(State.of(UUID.randomUUID())).date(DATE).updated(ImmutableList.of(TestId.of(2), TestId.of(3))).build();
repository.save(oldState).block();
repository.save(change1).block();
repository.save(change2).block();
@@ -249,9 +249,9 @@ public interface MailboxChangeRepositoryContract {
default void hasMoreChangesShouldBeTrueWhenMoreChanges() {
MailboxChangeRepository repository = mailboxChangeRepository();
- MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE.minusHours(2), ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
- MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(2), TestId.of(3)), ImmutableList.of(), ImmutableList.of());
- MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(), ImmutableList.of(TestId.of(2), TestId.of(3)), ImmutableList.of());
+ MailboxChange oldState = MailboxChange.builder().accountId(ACCOUNT_ID).state(STATE_0).date(DATE.minusHours(2)).created(ImmutableList.of(TestId.of(1))).build();
+ MailboxChange change1 = MailboxChange.builder().accountId(ACCOUNT_ID).state(State.of(UUID.randomUUID())).date(DATE.minusHours(1)).created(ImmutableList.of(TestId.of(2), TestId.of(3))).build();
+ MailboxChange change2 = MailboxChange.builder().accountId(ACCOUNT_ID).state(State.of(UUID.randomUUID())).date(DATE).updated(ImmutableList.of(TestId.of(2), TestId.of(3))).build();
repository.save(oldState).block();
repository.save(change1).block();
repository.save(change2).block();
@@ -264,9 +264,9 @@ public interface MailboxChangeRepositoryContract {
default void hasMoreChangesShouldBeFalseWhenNoMoreChanges() {
MailboxChangeRepository repository = mailboxChangeRepository();
- MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE.minusHours(2), ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
- MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(2), TestId.of(3)), ImmutableList.of(), ImmutableList.of());
- MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(), ImmutableList.of(TestId.of(2), TestId.of(3)), ImmutableList.of());
+ MailboxChange oldState = MailboxChange.builder().accountId(ACCOUNT_ID).state(STATE_0).date(DATE.minusHours(2)).created(ImmutableList.of(TestId.of(1))).build();
+ MailboxChange change1 = MailboxChange.builder().accountId(ACCOUNT_ID).state(State.of(UUID.randomUUID())).date(DATE.minusHours(1)).created(ImmutableList.of(TestId.of(2), TestId.of(3))).build();
+ MailboxChange change2 = MailboxChange.builder().accountId(ACCOUNT_ID).state(State.of(UUID.randomUUID())).date(DATE).updated(ImmutableList.of(TestId.of(2), TestId.of(3))).build();
repository.save(oldState).block();
repository.save(change1).block();
repository.save(change2).block();
@@ -279,10 +279,23 @@ public interface MailboxChangeRepositoryContract {
default void changesShouldBeStoredInTheirRespectiveType() {
MailboxChangeRepository repository = mailboxChangeRepository();
- MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE.minusHours(3), ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
- MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(2), ImmutableList.of(TestId.of(2), TestId.of(3), TestId.of(4), TestId.of(5)), ImmutableList.of(), ImmutableList.of());
- MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(6), TestId.of(7)), ImmutableList.of(TestId.of(2), TestId.of(3)), ImmutableList.of(TestId.of(4)));
- MailboxChange change3 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(TestId.of(8)), ImmutableList.of(TestId.of(6), TestId.of(7)), ImmutableList.of(TestId.of(5)));
+ MailboxChange oldState = MailboxChange.builder().accountId(ACCOUNT_ID).state(STATE_0).date(DATE.minusHours(3)).created(ImmutableList.of(TestId.of(1))).build();
+ MailboxChange change1 = MailboxChange.builder().accountId(ACCOUNT_ID).state(State.of(UUID.randomUUID())).date(DATE.minusHours(2)).created(ImmutableList.of(TestId.of(2), TestId.of(3), TestId.of(4), TestId.of(5))).build();
+ MailboxChange change2 = MailboxChange.builder()
+ .accountId(ACCOUNT_ID)
+ .state(State.of(UUID.randomUUID()))
+ .date(DATE.minusHours(1))
+ .created(ImmutableList.of(TestId.of(6), TestId.of(7)))
+ .updated(ImmutableList.of(TestId.of(2), TestId.of(3)))
+ .destroyed(ImmutableList.of(TestId.of(4))).build();
+ MailboxChange change3 = MailboxChange.builder()
+ .accountId(ACCOUNT_ID)
+ .state(State.of(UUID.randomUUID()))
+ .date(DATE)
+ .created(ImmutableList.of(TestId.of(8)))
+ .updated(ImmutableList.of(TestId.of(6), TestId.of(7)))
+ .destroyed(ImmutableList.of(TestId.of(5))).build();
+
repository.save(oldState).block();
repository.save(change1).block();
repository.save(change2).block();
@@ -301,9 +314,15 @@ public interface MailboxChangeRepositoryContract {
default void getChangesShouldIgnoreDuplicatedValues() {
MailboxChangeRepository repository = mailboxChangeRepository();
- MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE.minusHours(2), ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
- MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(), ImmutableList.of(TestId.of(1), TestId.of(2)), ImmutableList.of());
- MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(TestId.of(3)), ImmutableList.of(TestId.of(1), TestId.of(2)), ImmutableList.of());
+ MailboxChange oldState = MailboxChange.created(ACCOUNT_ID, STATE_0, DATE.minusHours(2), ImmutableList.of(TestId.of(1))).build();
+ MailboxChange change1 = MailboxChange.updated(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(1), TestId.of(2))).build();
+ MailboxChange change2 = MailboxChange.builder()
+ .accountId(ACCOUNT_ID)
+ .state(State.of(UUID.randomUUID()))
+ .date(DATE)
+ .created(ImmutableList.of(TestId.of(3)))
+ .updated(ImmutableList.of(TestId.of(1), TestId.of(2)))
+ .build();
repository.save(oldState).block();
repository.save(change1).block();
@@ -317,11 +336,32 @@ public interface MailboxChangeRepositoryContract {
}
@Test
+ default void getChangesShouldReturnDelegatedChanges() {
+ MailboxChangeRepository repository = mailboxChangeRepository();
+
+ MailboxChange oldState = MailboxChange.created(ACCOUNT_ID, STATE_0, DATE.minusHours(2), ImmutableList.of(TestId.of(1))).build();
+ MailboxChange change1 = MailboxChange.builder()
+ .accountId(ACCOUNT_ID)
+ .state(State.of(UUID.randomUUID()))
+ .date(DATE.minusHours(1))
+ .updated(ImmutableList.of(TestId.of(1)))
+ .delegated()
+ .build();
+
+ repository.save(oldState);
+ repository.save(change1);
+
+ assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.empty()).block().getUpdated())
+ .containsExactly(TestId.of(1));
+
+ }
+
+ @Test
default void getChangesShouldFailWhenInvalidMaxChanges() {
MailboxChangeRepository repository = mailboxChangeRepository();
- MailboxChange currentState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE, ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
- MailboxChange change = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(TestId.of(2)), ImmutableList.of(), ImmutableList.of());
+ MailboxChange currentState = MailboxChange.builder().accountId(ACCOUNT_ID).state(STATE_0).date(DATE).created(ImmutableList.of(TestId.of(1))).build();
+ MailboxChange change = MailboxChange.builder().accountId(ACCOUNT_ID).state(State.of(UUID.randomUUID())).date(DATE).created(ImmutableList.of(TestId.of(2))).build();
repository.save(currentState).block();
repository.save(change).block();
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/MailboxChangesMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxChangesMethodContract.scala
index e7de455..e07eef1 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxChangesMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxChangesMethodContract.scala
@@ -27,11 +27,13 @@ import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
import io.restassured.RestAssured.{`given`, requestSpecification}
import io.restassured.builder.ResponseSpecBuilder
import io.restassured.http.ContentType.JSON
+import javax.mail.Flags
import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
import net.javacrumbs.jsonunit.core.Option.IGNORING_ARRAY_ORDER
import net.javacrumbs.jsonunit.core.internal.Options
import org.apache.http.HttpStatus.SC_OK
import org.apache.james.GuiceJamesServer
+import org.apache.james.core.Username
import org.apache.james.jmap.api.change.MailboxChange
import org.apache.james.jmap.api.change.MailboxChange.State
import org.apache.james.jmap.api.model.AccountId
@@ -41,12 +43,12 @@ import org.apache.james.jmap.http.UserCredential
import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ANDRE, ANDRE_ACCOUNT_ID, ANDRE_PASSWORD, BOB, BOB_PASSWORD, DOMAIN, authScheme, baseRequestSpecBuilder}
import org.apache.james.mailbox.MessageManager.AppendCommand
import org.apache.james.mailbox.model.MailboxACL.Right
-import org.apache.james.mailbox.model.{MailboxACL, MailboxId, MailboxPath}
+import org.apache.james.mailbox.model.{MailboxACL, MailboxId, MailboxPath, MessageId}
import org.apache.james.mime4j.dom.Message
import org.apache.james.modules.{ACLProbeImpl, MailboxProbeImpl}
import org.apache.james.utils.DataProbeImpl
import org.assertj.core.api.Assertions.assertThat
-import org.junit.jupiter.api.{BeforeEach, Disabled, Test}
+import org.junit.jupiter.api.{BeforeEach, Disabled, Nested, Test}
import play.api.libs.json.{JsString, Json}
import scala.jdk.CollectionConverters._
@@ -80,7 +82,7 @@ trait MailboxChangesMethodContract {
val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
provisionSystemMailboxes(server)
- val oldState: State = storeReferenceState(server)
+ val oldState: State = storeReferenceState(server, BOB)
val mailboxId1: String = mailboxProbe
.createMailbox(MailboxPath.forUser(BOB, "mailbox1"))
@@ -148,7 +150,7 @@ trait MailboxChangesMethodContract {
.createMailbox(path)
.serialize
- val oldState: State = storeReferenceState(server)
+ val oldState: State = storeReferenceState(server, BOB)
renameMailbox(mailboxId, "mailbox11")
@@ -207,7 +209,7 @@ trait MailboxChangesMethodContract {
.createMailbox(path)
.serialize
- val oldState: State = storeReferenceState(server)
+ val oldState: State = storeReferenceState(server, BOB)
val message: Message = Message.Builder
.of
@@ -261,28 +263,26 @@ trait MailboxChangesMethodContract {
}
@Test
- @Disabled("Not implemented yet")
- def mailboxChangesShouldReturnUpdatedChangesWhenAppendMessageToDelegatedMailbox(server: GuiceJamesServer): Unit = {
+ def mailboxChangesShouldReturnUpdatedChangesWhenAddSeenFlag(server: GuiceJamesServer): Unit = {
val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
provisionSystemMailboxes(server)
- val oldState: State = storeReferenceState(server)
-
val path = MailboxPath.forUser(BOB, "mailbox1")
val mailboxId: String = mailboxProbe
.createMailbox(path)
.serialize
- server.getProbe(classOf[ACLProbeImpl])
- .replaceRights(path, ANDRE.asString, new MailboxACL.Rfc4314Rights(Right.Lookup, Right.Read))
-
val message: Message = Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build
- mailboxProbe.appendMessage(BOB.asString(), path, AppendCommand.from(message))
+ val messageId: MessageId = mailboxProbe.appendMessage(BOB.asString(), path, AppendCommand.from(message)).getMessageId
+
+ val oldState: State = storeReferenceState(server, BOB)
+
+ markEmailAsSeen(messageId)
val request =
s"""{
@@ -290,25 +290,23 @@ trait MailboxChangesMethodContract {
| "methodCalls": [[
| "Mailbox/changes",
| {
- | "accountId": "$ANDRE_ACCOUNT_ID",
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "sinceState": "${oldState.getValue}"
| },
| "c1"]]
|}""".stripMargin
- val response = `given`(
- baseRequestSpecBuilder(server)
- .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
- .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
- .setBody(request)
- .build, new ResponseSpecBuilder().build)
+ val response = `given`
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(request)
+ .when
.post
- .`then`
- .statusCode(SC_OK)
- .contentType(JSON)
- .extract
- .body
- .asString
+ .`then`
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract
+ .body
+ .asString
assertThatJson(response)
.whenIgnoringPaths("methodResponses[0][1].newState")
@@ -318,7 +316,7 @@ trait MailboxChangesMethodContract {
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [
| [ "Mailbox/changes", {
- | "accountId": "$ANDRE_ACCOUNT_ID",
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "oldState": "${oldState.getValue}",
| "hasMoreChanges": false,
| "updatedProperties": [],
@@ -331,6 +329,553 @@ trait MailboxChangesMethodContract {
}
@Test
+ def mailboxChangesShouldReturnUpdatedChangesWhenRemoveSeenFlag(server: GuiceJamesServer): Unit = {
+ val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
+
+ provisionSystemMailboxes(server)
+
+ val path = MailboxPath.forUser(BOB, "mailbox1")
+ val mailboxId: String = mailboxProbe
+ .createMailbox(path)
+ .serialize
+
+ server.getProbe(classOf[ACLProbeImpl])
+ .replaceRights(path, ANDRE.asString, new MailboxACL.Rfc4314Rights(Right.Lookup, Right.Read))
+
+ val messageId: MessageId = mailboxProbe.appendMessage(BOB.asString(), path,
+ AppendCommand.builder()
+ .withFlags(new Flags(Flags.Flag.SEEN))
+ .build("header: value\r\n\r\nbody"))
+ .getMessageId
+
+ val oldState: State = storeReferenceState(server, BOB)
+
+ markEmailAsNotSeen(messageId)
+
+ val request =
+ s"""{
+ | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [[
+ | "Mailbox/changes",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "sinceState": "${oldState.getValue}"
+ | },
+ | "c1"]]
+ |}""".stripMargin
+
+ val response = `given`
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(request)
+ .when
+ .post
+ .`then`
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract
+ .body
+ .asString
+
+ assertThatJson(response)
+ .whenIgnoringPaths("methodResponses[0][1].newState")
+ .withOptions(new Options(IGNORING_ARRAY_ORDER))
+ .isEqualTo(
+ s"""{
+ | "sessionState": "${SESSION_STATE.value}",
+ | "methodResponses": [
+ | ["Mailbox/changes", {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "oldState": "${oldState.getValue}",
+ | "hasMoreChanges": false,
+ | "updatedProperties": [],
+ | "created": [],
+ | "updated": ["$mailboxId"],
+ | "destroyed": []
+ | }, "c1"]
+ | ]
+ |}""".stripMargin)
+ }
+
+ @Test
+ def mailboxChangesShouldReturnUpdatedChangesWhenDestroyEmail(server: GuiceJamesServer): Unit = {
+ val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
+
+ provisionSystemMailboxes(server)
+
+ val path = MailboxPath.forUser(BOB, "mailbox1")
+ val mailboxId: String = mailboxProbe
+ .createMailbox(path)
+ .serialize
+
+ val message: Message = Message.Builder
+ .of
+ .setSubject("test")
+ .setBody("testmail", StandardCharsets.UTF_8)
+ .build
+ val messageId: MessageId = mailboxProbe.appendMessage(BOB.asString(), path, AppendCommand.from(message)).getMessageId
+
+ val oldState: State = storeReferenceState(server, BOB)
+
+ destroyEmail(messageId)
+
+ val request =
+ s"""{
+ | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [[
+ | "Mailbox/changes",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "sinceState": "${oldState.getValue}"
+ | },
+ | "c1"]]
+ |}""".stripMargin
+
+ val response = `given`
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(request)
+ .when
+ .post
+ .`then`
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract
+ .body
+ .asString
+
+ assertThatJson(response)
+ .whenIgnoringPaths("methodResponses[0][1].newState")
+ .withOptions(new Options(IGNORING_ARRAY_ORDER))
+ .isEqualTo(
+ s"""{
+ | "sessionState": "${SESSION_STATE.value}",
+ | "methodResponses": [
+ | ["Mailbox/changes", {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "oldState": "${oldState.getValue}",
+ | "hasMoreChanges": false,
+ | "updatedProperties": [],
+ | "created": [],
+ | "updated": ["$mailboxId"],
+ | "destroyed": []
+ | }, "c1"]
+ | ]
+ |}""".stripMargin)
+ }
+
+ @Nested
+ class MailboxDelegationTest {
+ @Test
+ def mailboxChangesShouldReturnUpdatedChangesWhenAppendMessageToMailbox(server: GuiceJamesServer): Unit = {
+ val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
+
+ provisionSystemMailboxes(server)
+
+ val path = MailboxPath.forUser(BOB, "mailbox1")
+ val mailboxId: String = mailboxProbe
+ .createMailbox(path)
+ .serialize
+
+ server.getProbe(classOf[ACLProbeImpl])
+ .replaceRights(path, ANDRE.asString, new MailboxACL.Rfc4314Rights(Right.Lookup, Right.Read))
+
+ val oldState: State = storeReferenceState(server, ANDRE)
+
+ val message: Message = Message.Builder
+ .of
+ .setSubject("test")
+ .setBody("testmail", StandardCharsets.UTF_8)
+ .build
+ mailboxProbe.appendMessage(BOB.asString(), path, AppendCommand.from(message))
+
+ val request =
+ s"""{
+ | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [[
+ | "Mailbox/changes",
+ | {
+ | "accountId": "$ANDRE_ACCOUNT_ID",
+ | "sinceState": "${oldState.getValue}"
+ | },
+ | "c1"]]
+ |}""".stripMargin
+
+ val response = `given`(
+ baseRequestSpecBuilder(server)
+ .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
+ .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .setBody(request)
+ .build, new ResponseSpecBuilder().build)
+ .post
+ .`then`
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract
+ .body
+ .asString
+
+ assertThatJson(response)
+ .whenIgnoringPaths("methodResponses[0][1].newState")
+ .withOptions(new Options(IGNORING_ARRAY_ORDER))
+ .isEqualTo(
+ s"""{
+ | "sessionState": "${SESSION_STATE.value}",
+ | "methodResponses": [
+ | [ "Mailbox/changes", {
+ | "accountId": "$ANDRE_ACCOUNT_ID",
+ | "oldState": "${oldState.getValue}",
+ | "hasMoreChanges": false,
+ | "updatedProperties": [],
+ | "created": [],
+ | "updated": ["$mailboxId"],
+ | "destroyed": []
+ | }, "c1"]
+ | ]
+ |}""".stripMargin)
+ }
+
+ @Test
+ def mailboxChangesShouldReturnUpdatedChangesWhenRenameMailbox(server: GuiceJamesServer): Unit = {
+ val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
+
+ provisionSystemMailboxes(server)
+
+ val path = MailboxPath.forUser(BOB, "mailbox1")
+ val mailboxId: String = mailboxProbe
+ .createMailbox(path)
+ .serialize
+
+ server.getProbe(classOf[ACLProbeImpl])
+ .replaceRights(path, ANDRE.asString, new MailboxACL.Rfc4314Rights(Right.Lookup, Right.Read))
+
+ val oldState: State = storeReferenceState(server, ANDRE)
+
+ renameMailbox(mailboxId, "mailbox11")
+
+ val request =
+ s"""{
+ | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [[
+ | "Mailbox/changes",
+ | {
+ | "accountId": "$ANDRE_ACCOUNT_ID",
+ | "sinceState": "${oldState.getValue}"
+ | },
+ | "c1"]]
+ |}""".stripMargin
+
+ val response = `given`(
+ baseRequestSpecBuilder(server)
+ .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
+ .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .setBody(request)
+ .build, new ResponseSpecBuilder().build)
+ .post
+ .`then`
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract
+ .body
+ .asString
+
+ assertThatJson(response)
+ .whenIgnoringPaths("methodResponses[0][1].newState")
+ .withOptions(new Options(IGNORING_ARRAY_ORDER))
+ .isEqualTo(
+ s"""{
+ | "sessionState": "${SESSION_STATE.value}",
+ | "methodResponses": [
+ | [ "Mailbox/changes", {
+ | "accountId": "$ANDRE_ACCOUNT_ID",
+ | "oldState": "${oldState.getValue}",
+ | "hasMoreChanges": false,
+ | "updatedProperties": [],
+ | "created": [],
+ | "updated": ["$mailboxId"],
+ | "destroyed": []
+ | }, "c1"]
+ | ]
+ |}""".stripMargin)
+ }
+
+ @Test
+ def mailboxChangesShouldReturnUpdatedChangesWhenAddSeenFlag(server: GuiceJamesServer): Unit = {
+ val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
+
+ provisionSystemMailboxes(server)
+
+ val path = MailboxPath.forUser(BOB, "mailbox1")
+ val mailboxId: String = mailboxProbe
+ .createMailbox(path)
+ .serialize
+
+ server.getProbe(classOf[ACLProbeImpl])
+ .replaceRights(path, ANDRE.asString, new MailboxACL.Rfc4314Rights(Right.Lookup, Right.Read))
+
+ val message: Message = Message.Builder
+ .of
+ .setSubject("test")
+ .setBody("testmail", StandardCharsets.UTF_8)
+ .build
+ val messageId: MessageId = mailboxProbe.appendMessage(BOB.asString(), path, AppendCommand.from(message)).getMessageId
+
+ val oldState: State = storeReferenceState(server, ANDRE)
+
+ markEmailAsSeen(messageId)
+
+ val request =
+ s"""{
+ | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [[
+ | "Mailbox/changes",
+ | {
+ | "accountId": "$ANDRE_ACCOUNT_ID",
+ | "sinceState": "${oldState.getValue}"
+ | },
+ | "c1"]]
+ |}""".stripMargin
+
+ val response = `given`(
+ baseRequestSpecBuilder(server)
+ .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
+ .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .setBody(request)
+ .build, new ResponseSpecBuilder().build)
+ .post
+ .`then`
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract
+ .body
+ .asString
+
+ assertThatJson(response)
+ .whenIgnoringPaths("methodResponses[0][1].newState")
+ .withOptions(new Options(IGNORING_ARRAY_ORDER))
+ .isEqualTo(
+ s"""{
+ | "sessionState": "${SESSION_STATE.value}",
+ | "methodResponses": [
+ | [ "Mailbox/changes", {
+ | "accountId": "$ANDRE_ACCOUNT_ID",
+ | "oldState": "${oldState.getValue}",
+ | "hasMoreChanges": false,
+ | "updatedProperties": [],
+ | "created": [],
+ | "updated": ["$mailboxId"],
+ | "destroyed": []
+ | }, "c1"]
+ | ]
+ |}""".stripMargin)
+ }
+
+ @Test
+ def mailboxChangesShouldReturnUpdatedChangesWhenRemoveSeenFlag(server: GuiceJamesServer): Unit = {
+ val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
+
+ provisionSystemMailboxes(server)
+
+ val path = MailboxPath.forUser(BOB, "mailbox1")
+ val mailboxId: String = mailboxProbe
+ .createMailbox(path)
+ .serialize
+
+ server.getProbe(classOf[ACLProbeImpl])
+ .replaceRights(path, ANDRE.asString, new MailboxACL.Rfc4314Rights(Right.Lookup, Right.Read))
+
+ val messageId: MessageId = mailboxProbe.appendMessage(BOB.asString(), path,
+ AppendCommand.builder()
+ .withFlags(new Flags(Flags.Flag.SEEN))
+ .build("header: value\r\n\r\nbody"))
+ .getMessageId
+
+ val oldState: State = storeReferenceState(server, ANDRE)
+
+ markEmailAsNotSeen(messageId)
+
+ val request =
+ s"""{
+ | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [[
+ | "Mailbox/changes",
+ | {
+ | "accountId": "$ANDRE_ACCOUNT_ID",
+ | "sinceState": "${oldState.getValue}"
+ | },
+ | "c1"]]
+ |}""".stripMargin
+
+ val response = `given`(
+ baseRequestSpecBuilder(server)
+ .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
+ .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .setBody(request)
+ .build, new ResponseSpecBuilder().build)
+ .post
+ .`then`
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract
+ .body
+ .asString
+
+ assertThatJson(response)
+ .whenIgnoringPaths("methodResponses[0][1].newState")
+ .withOptions(new Options(IGNORING_ARRAY_ORDER))
+ .isEqualTo(
+ s"""{
+ | "sessionState": "${SESSION_STATE.value}",
+ | "methodResponses": [
+ | [ "Mailbox/changes", {
+ | "accountId": "$ANDRE_ACCOUNT_ID",
+ | "oldState": "${oldState.getValue}",
+ | "hasMoreChanges": false,
+ | "updatedProperties": [],
+ | "created": [],
+ | "updated": ["$mailboxId"],
+ | "destroyed": []
+ | }, "c1"]
+ | ]
+ |}""".stripMargin)
+ }
+
+ @Test
+ def mailboxChangesShouldReturnUpdatedChangesWhenDestroyEmail(server: GuiceJamesServer): Unit = {
+ val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
+
+ provisionSystemMailboxes(server)
+
+ val path = MailboxPath.forUser(BOB, "mailbox1")
+ val mailboxId: String = mailboxProbe
+ .createMailbox(path)
+ .serialize
+
+ server.getProbe(classOf[ACLProbeImpl])
+ .replaceRights(path, ANDRE.asString, new MailboxACL.Rfc4314Rights(Right.Lookup, Right.Read))
+
+ val message: Message = Message.Builder
+ .of
+ .setSubject("test")
+ .setBody("testmail", StandardCharsets.UTF_8)
+ .build
+ val messageId: MessageId = mailboxProbe.appendMessage(BOB.asString(), path, AppendCommand.from(message)).getMessageId
+
+ val oldState: State = storeReferenceState(server, ANDRE)
+
+ destroyEmail(messageId)
+
+ val request =
+ s"""{
+ | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [[
+ | "Mailbox/changes",
+ | {
+ | "accountId": "$ANDRE_ACCOUNT_ID",
+ | "sinceState": "${oldState.getValue}"
+ | },
+ | "c1"]]
+ |}""".stripMargin
+
+ val response = `given`(
+ baseRequestSpecBuilder(server)
+ .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
+ .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .setBody(request)
+ .build, new ResponseSpecBuilder().build)
+ .post
+ .`then`
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract
+ .body
+ .asString
+
+ assertThatJson(response)
+ .whenIgnoringPaths("methodResponses[0][1].newState")
+ .withOptions(new Options(IGNORING_ARRAY_ORDER))
+ .isEqualTo(
+ s"""{
+ | "sessionState": "${SESSION_STATE.value}",
+ | "methodResponses": [
+ | [ "Mailbox/changes", {
+ | "accountId": "$ANDRE_ACCOUNT_ID",
+ | "oldState": "${oldState.getValue}",
+ | "hasMoreChanges": false,
+ | "updatedProperties": [],
+ | "created": [],
+ | "updated": ["$mailboxId"],
+ | "destroyed": []
+ | }, "c1"]
+ | ]
+ |}""".stripMargin)
+ }
+
+ @Test
+ @Disabled("Not implemented yet")
+ def mailboxChangesShouldReturnUpdatedChangesWhenDestroyDelegatedMailbox(server: GuiceJamesServer): Unit = {
+ val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
+
+ provisionSystemMailboxes(server)
+
+ val path = MailboxPath.forUser(BOB, "mailbox1")
+ val mailboxId: String = mailboxProbe
+ .createMailbox(path)
+ .serialize
+
+ server.getProbe(classOf[ACLProbeImpl])
+ .replaceRights(path, ANDRE.asString, new MailboxACL.Rfc4314Rights(Right.Lookup, Right.Read))
+
+ val oldState: State = storeReferenceState(server, ANDRE)
+
+ destroyMailbox(mailboxId)
+
+ val request =
+ s"""{
+ | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [[
+ | "Mailbox/changes",
+ | {
+ | "accountId": "$ANDRE_ACCOUNT_ID",
+ | "sinceState": "${oldState.getValue}"
+ | },
+ | "c1"]]
+ |}""".stripMargin
+
+ val response = `given`(
+ baseRequestSpecBuilder(server)
+ .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
+ .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .setBody(request)
+ .build, new ResponseSpecBuilder().build)
+ .post
+ .`then`
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract
+ .body
+ .asString
+
+ assertThatJson(response)
+ .whenIgnoringPaths("methodResponses[0][1].newState")
+ .withOptions(new Options(IGNORING_ARRAY_ORDER))
+ .isEqualTo(
+ s"""{
+ | "sessionState": "${SESSION_STATE.value}",
+ | "methodResponses": [
+ | [ "Mailbox/changes", {
+ | "accountId": "$ANDRE_ACCOUNT_ID",
+ | "oldState": "${oldState.getValue}",
+ | "hasMoreChanges": false,
+ | "updatedProperties": [],
+ | "created": [],
+ | "updated": [],
+ | "destroyed": ["$mailboxId"]
+ | }, "c1"]
+ | ]
+ |}""".stripMargin)
+ }
+ }
+
+ @Test
def mailboxChangesShouldReturnDestroyedChanges(server: GuiceJamesServer): Unit = {
val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
@@ -341,7 +886,7 @@ trait MailboxChangesMethodContract {
.createMailbox(path)
.serialize
- val oldState: State = storeReferenceState(server)
+ val oldState: State = storeReferenceState(server, BOB)
mailboxProbe
.deleteMailbox(path.getNamespace, BOB.asString(), path.getName)
@@ -396,7 +941,7 @@ trait MailboxChangesMethodContract {
provisionSystemMailboxes(server)
- val oldState: State = storeReferenceState(server)
+ val oldState: State = storeReferenceState(server, BOB)
val path1 = MailboxPath.forUser(BOB, "mailbox1")
val mailboxId1: String = mailboxProbe
@@ -468,7 +1013,7 @@ trait MailboxChangesMethodContract {
val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
provisionSystemMailboxes(server)
- val oldState: State = storeReferenceState(server)
+ val oldState: State = storeReferenceState(server, BOB)
val mailboxId1: String = mailboxProbe
.createMailbox(MailboxPath.forUser(BOB, "mailbox1"))
@@ -540,7 +1085,7 @@ trait MailboxChangesMethodContract {
@Test
def mailboxChangesShouldFailWhenAccountIdNotFound(server: GuiceJamesServer): Unit = {
- val oldState: State = storeReferenceState(server)
+ val oldState: State = storeReferenceState(server, BOB)
val request =
s"""{
@@ -627,7 +1172,7 @@ trait MailboxChangesMethodContract {
def mailboxChangesShouldReturnNoChangesWhenNoNewerState(server: GuiceJamesServer): Unit = {
provisionSystemMailboxes(server)
- val oldState: State = storeReferenceState(server)
+ val oldState: State = storeReferenceState(server, BOB)
val request =
s"""{
@@ -678,7 +1223,7 @@ trait MailboxChangesMethodContract {
val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
provisionSystemMailboxes(server)
- val oldState: State = storeReferenceState(server)
+ val oldState: State = storeReferenceState(server, BOB)
mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "mailbox1"))
mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "mailbox2"))
@@ -720,7 +1265,7 @@ trait MailboxChangesMethodContract {
val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
provisionSystemMailboxes(server)
- val oldState: State = storeReferenceState(server)
+ val oldState: State = storeReferenceState(server, BOB)
mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "mailbox1"))
mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "mailbox2"))
@@ -827,10 +1372,111 @@ trait MailboxChangesMethodContract {
.contentType(JSON)
}
- private def storeReferenceState(server: GuiceJamesServer): State = {
+ private def destroyMailbox(mailboxId: String): Unit = {
+ val request =
+ s"""
+ |{
+ | "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ],
+ | "methodCalls": [[
+ | "Mailbox/set", {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "destroy": ["$mailboxId"]
+ | }, "c1"]
+ | ]
+ |}
+ |""".stripMargin
+
+ `given`
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(request)
+ .when
+ .post
+ .`then`
+ .log().ifValidationFails()
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ }
+
+ private def markEmailAsSeen(messageId: MessageId): Unit = {
+ val request = String.format(
+ s"""{
+ | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [
+ | ["Email/set", {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "update": {
+ | "${messageId.serialize}": {
+ | "keywords": {
+ | "$$seen": true
+ | }
+ | }
+ | }
+ | }, "c1"]]
+ |}""".stripMargin)
+
+ `given`
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(request)
+ .when
+ .post
+ .`then`
+ .log().ifValidationFails()
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ }
+
+ private def markEmailAsNotSeen(messageId: MessageId): Unit = {
+ val request = String.format(
+ s"""{
+ | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [
+ | ["Email/set", {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "update": {
+ | "${messageId.serialize}": {
+ | "keywords/$$seen": null
+ | }
+ | }
+ | }, "c1"]]
+ |}""".stripMargin)
+
+ `given`
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(request)
+ .when
+ .post
+ .`then`
+ .log().ifValidationFails()
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ }
+
+ private def destroyEmail(messageId: MessageId): Unit = {
+ val request = String.format(
+ s"""{
+ | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [
+ | ["Email/set", {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "destroy": ["${messageId.serialize}"]
+ | }, "c1"]]
+ |}""".stripMargin)
+
+ `given`
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(request)
+ .when
+ .post
+ .`then`
+ .log().ifValidationFails()
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ }
+
+ private def storeReferenceState(server: GuiceJamesServer, username: Username): State = {
val state: State = State.of(UUID.randomUUID())
val jmapGuiceProbe: JmapGuiceProbe = server.getProbe(classOf[JmapGuiceProbe])
- jmapGuiceProbe.saveMailboxChange(MailboxChange.of(AccountId.fromUsername(BOB), state, ZonedDateTime.now(), List().asJava, List(TestId.of(0)).asJava, List().asJava))
+ jmapGuiceProbe.saveMailboxChange(MailboxChange.updated(AccountId.fromUsername(username), state, ZonedDateTime.now(), List(TestId.of(0)).asJava).build)
state
}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/MailboxChangeListener.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/MailboxChangeListener.scala
index c251fb5..b577eb8 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/MailboxChangeListener.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/MailboxChangeListener.scala
@@ -21,21 +21,29 @@ package org.apache.james.jmap.change
import javax.inject.Inject
import org.apache.james.jmap.api.change.{MailboxChange, MailboxChangeRepository}
-import org.apache.james.mailbox.events.MailboxListener.ReactiveGroupMailboxListener
+import org.apache.james.mailbox.MailboxManager
+import org.apache.james.mailbox.events.MailboxListener.{MailboxEvent, ReactiveGroupMailboxListener}
import org.apache.james.mailbox.events.{Event, Group}
import org.reactivestreams.Publisher
-import reactor.core.scala.publisher.SMono
+import reactor.core.scala.publisher.{SFlux, SMono}
+
+import scala.jdk.CollectionConverters._
case class MailboxChangeListenerGroup() extends Group {}
-case class MailboxChangeListener @Inject() (mailboxChangeRepository: MailboxChangeRepository) extends ReactiveGroupMailboxListener {
+case class MailboxChangeListener @Inject() (mailboxChangeRepository: MailboxChangeRepository,
+ mailboxManager: MailboxManager) extends ReactiveGroupMailboxListener {
- override def reactiveEvent(event: Event): Publisher[Void] =
- MailboxChange.fromEvent(event)
- .map(mailboxChangeRepository.save(_))
+ override def reactiveEvent(event: Event): Publisher[Void] = {
+ MailboxChange.fromEvent(event, mailboxManager)
+ .map(changes => SFlux.fromIterable(changes.asScala)
+ .map(change => mailboxChangeRepository.save(change))
+ .`then`()
+ .`then`(SMono.empty[Void]).asJava)
.orElse(SMono.empty[Void].asJava)
+ }
override def getDefaultGroup: Group = MailboxChangeListenerGroup()
- override def isHandling(event: Event): Boolean = MailboxChange.fromEvent(event).isPresent
+ override def isHandling(event: Event): Boolean = event.isInstanceOf[MailboxEvent]
}
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/MailboxChangeListenerTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/MailboxChangeListenerTest.scala
index 4c7acb8..aecbfc7 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/MailboxChangeListenerTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/MailboxChangeListenerTest.scala
@@ -63,14 +63,14 @@ class MailboxChangeListenerTest {
mailboxManager = resources.getMailboxManager
repository = new MemoryMailboxChangeRepository()
- listener = MailboxChangeListener(repository)
+ listener = MailboxChangeListener(repository, mailboxManager)
resources.getEventBus.register(listener)
}
@Test
def createMailboxShouldStoreCreatedEvent(): Unit = {
val state = State.of(UUID.randomUUID)
- repository.save(MailboxChange.of(ACCOUNT_ID, state, ZonedDateTime.now, List[MailboxId](TestId.of(0)).asJava, List().asJava, List().asJava)).block()
+ repository.save(MailboxChange.created(ACCOUNT_ID, state, ZonedDateTime.now, List[MailboxId](TestId.of(0)).asJava).build).block()
val mailboxSession = MailboxSessionUtil.create(BOB)
val inboxId: MailboxId = mailboxManager.createMailbox(MailboxPath.inbox(BOB), mailboxSession).get
@@ -87,7 +87,7 @@ class MailboxChangeListenerTest {
val inboxId: MailboxId = mailboxManager.createMailbox(path, mailboxSession).get
val state = State.of(UUID.randomUUID)
- repository.save(MailboxChange.of(ACCOUNT_ID, state, ZonedDateTime.now, List[MailboxId](TestId.of(0)).asJava, List().asJava, List().asJava)).block()
+ repository.save(MailboxChange.created(ACCOUNT_ID, state, ZonedDateTime.now, List[MailboxId](TestId.of(0)).asJava).build).block()
mailboxManager.renameMailbox(path, newPath, mailboxSession)
@@ -98,13 +98,13 @@ class MailboxChangeListenerTest {
@Test
def updateMailboxACLShouldStoreUpdatedEvent(): Unit = {
val state = State.of(UUID.randomUUID)
- repository.save(MailboxChange.of(ACCOUNT_ID, state, ZonedDateTime.now, List[MailboxId](TestId.of(0)).asJava, List().asJava, List().asJava)).block()
+ repository.save(MailboxChange.created(ACCOUNT_ID, state, ZonedDateTime.now, List[MailboxId](TestId.of(0)).asJava).build).block()
val mailboxSession = MailboxSessionUtil.create(BOB)
val path = MailboxPath.inbox(BOB)
val inboxId: MailboxId = mailboxManager.createMailbox(MailboxPath.inbox(BOB), mailboxSession).get
- mailboxManager.applyRightsCommand(path, MailboxACL.command().forUser(ALICE).rights(MailboxACL.Right.Read).asAddition(), mailboxSession);
+ mailboxManager.applyRightsCommand(path, MailboxACL.command().forUser(ALICE).rights(MailboxACL.Right.Read).asAddition(), mailboxSession)
assertThat(repository.getSinceState(ACCOUNT_ID, state, None.toJava).block().getUpdated)
.containsExactly(inboxId)
@@ -117,7 +117,9 @@ class MailboxChangeListenerTest {
val inboxId: MailboxId = mailboxManager.createMailbox(path, mailboxSession).get
val state = State.of(UUID.randomUUID)
- repository.save(MailboxChange.of(ACCOUNT_ID, state, ZonedDateTime.now, List[MailboxId](TestId.of(0)).asJava, List().asJava, List().asJava)).block()
+ repository.save(MailboxChange.created(ACCOUNT_ID, state, ZonedDateTime.now, List[MailboxId](TestId.of(0)).asJava).build).block()
+
+ mailboxManager.applyRightsCommand(path, MailboxACL.command().forUser(ALICE).rights(MailboxACL.Right.Read).asAddition(), mailboxSession)
mailboxManager
.getMailbox(inboxId, mailboxSession)
@@ -136,7 +138,7 @@ class MailboxChangeListenerTest {
messageManager.appendMessage(AppendCommand.builder().build("header: value\r\n\r\nbody"), mailboxSession)
val state = State.of(UUID.randomUUID)
- repository.save(MailboxChange.of(ACCOUNT_ID, state, ZonedDateTime.now, List[MailboxId](TestId.of(0)).asJava, List().asJava, List().asJava)).block()
+ repository.save(MailboxChange.created(ACCOUNT_ID, state, ZonedDateTime.now, List[MailboxId](TestId.of(0)).asJava).build).block()
messageManager.setFlags(new Flags(Flags.Flag.SEEN), FlagsUpdateMode.ADD, MessageRange.all(), mailboxSession)
@@ -155,7 +157,7 @@ class MailboxChangeListenerTest {
.build("header: value\r\n\r\nbody"), mailboxSession)
val state = State.of(UUID.randomUUID)
- repository.save(MailboxChange.of(ACCOUNT_ID, state, ZonedDateTime.now, List[MailboxId](TestId.of(0)).asJava, List().asJava, List().asJava)).block()
+ repository.save(MailboxChange.created(ACCOUNT_ID, state, ZonedDateTime.now, List[MailboxId](TestId.of(0)).asJava).build).block()
messageManager.setFlags(new Flags(Flags.Flag.SEEN), FlagsUpdateMode.REMOVE, MessageRange.all(), mailboxSession)
@@ -172,7 +174,7 @@ class MailboxChangeListenerTest {
messageManager.appendMessage(AppendCommand.builder().build("header: value\r\n\r\nbody"), mailboxSession)
val state = State.of(UUID.randomUUID)
- repository.save(MailboxChange.of(ACCOUNT_ID, state, ZonedDateTime.now, List[MailboxId](TestId.of(0)).asJava, List().asJava, List().asJava)).block()
+ repository.save(MailboxChange.created(ACCOUNT_ID, state, ZonedDateTime.now, List[MailboxId](TestId.of(0)).asJava).build).block()
messageManager.setFlags(new Flags(Flags.Flag.ANSWERED), FlagsUpdateMode.ADD, MessageRange.all(), mailboxSession)
@@ -191,7 +193,7 @@ class MailboxChangeListenerTest {
.build("header: value\r\n\r\nbody"), mailboxSession)
val state = State.of(UUID.randomUUID)
- repository.save(MailboxChange.of(ACCOUNT_ID, state, ZonedDateTime.now, List[MailboxId](TestId.of(0)).asJava, List().asJava, List().asJava)).block()
+ repository.save(MailboxChange.created(ACCOUNT_ID, state, ZonedDateTime.now, List[MailboxId](TestId.of(0)).asJava).build).block()
messageManager.setFlags(new Flags(Flags.Flag.DELETED), FlagsUpdateMode.REPLACE, MessageRange.all(), mailboxSession)
@@ -208,7 +210,7 @@ class MailboxChangeListenerTest {
val appendResult: AppendResult = messageManager.appendMessage(AppendCommand.builder().build("header: value\r\n\r\nbody"), mailboxSession)
val state = State.of(UUID.randomUUID)
- repository.save(MailboxChange.of(ACCOUNT_ID, state, ZonedDateTime.now, List[MailboxId](TestId.of(0)).asJava, List().asJava, List().asJava)).block()
+ repository.save(MailboxChange.created(ACCOUNT_ID, state, ZonedDateTime.now, List[MailboxId](TestId.of(0)).asJava).build).block()
messageManager.delete(List(appendResult.getId.getUid).asJava, mailboxSession)
assertThat(repository.getSinceState(ACCOUNT_ID, state, None.toJava).block().getUpdated)
@@ -222,7 +224,7 @@ class MailboxChangeListenerTest {
val inboxId: MailboxId = mailboxManager.createMailbox(path, mailboxSession).get
val state = State.of(UUID.randomUUID)
- repository.save(MailboxChange.of(ACCOUNT_ID, state, ZonedDateTime.now, List[MailboxId](TestId.of(0)).asJava, List().asJava, List().asJava)).block()
+ repository.save(MailboxChange.created(ACCOUNT_ID, state, ZonedDateTime.now, List[MailboxId](TestId.of(0)).asJava).build).block()
mailboxManager.deleteMailbox(inboxId, mailboxSession)
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org