You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by bt...@apache.org on 2019/03/14 02:04:55 UTC
[james-project] 03/07: JAMES-2663 Vault route: restore by query
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 8174c65554645bc6487e9996b3cd8e711dda3672
Author: Tran Tien Duc <dt...@linagora.com>
AuthorDate: Thu Mar 7 11:51:35 2019 +0700
JAMES-2663 Vault route: restore by query
---
.../java/org/apache/james/vault/search/Query.java | 2 +-
.../integration/DeletedMessagesVaultTest.java | 6 +
.../routes/DeletedMessagesVaultRestoreTask.java | 7 +-
.../vault/routes/DeletedMessagesVaultRoutes.java | 47 +-
.../webadmin/vault/routes/RestoreService.java | 4 +-
.../webadmin/vault/routes/query/QueryElement.java | 2 +-
.../vault/routes/query/QueryTranslator.java | 21 +-
.../routes/DeletedMessagesVaultRoutesTest.java | 1270 +++++++++++++++++++-
.../vault/routes/query/QueryTranslatorTest.java | 7 +
src/site/markdown/server/manage-webadmin.md | 101 +-
10 files changed, 1402 insertions(+), 65 deletions(-)
diff --git a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/search/Query.java b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/search/Query.java
index 8595a43..3092cb9 100644
--- a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/search/Query.java
+++ b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/search/Query.java
@@ -30,7 +30,7 @@ public class Query {
public static final Query ALL = new Query(ImmutableList.of());
private static final Predicate<DeletedMessage> MATCH_ALL = any -> true;
- public static Query of(List<Criterion> criteria) {
+ public static Query and(List<Criterion> criteria) {
return new Query(criteria);
}
diff --git a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/DeletedMessagesVaultTest.java b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/DeletedMessagesVaultTest.java
index 55476f0..4336a08 100644
--- a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/DeletedMessagesVaultTest.java
+++ b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/DeletedMessagesVaultTest.java
@@ -76,6 +76,11 @@ public abstract class DeletedMessagesVaultTest {
private static final ConditionFactory WAIT_TWO_MINUTES = calmlyAwait.atMost(Duration.TWO_MINUTES);
private static final String SUBJECT = "This mail will be restored from the vault!!";
private static final String MAILBOX_NAME = "toBeDeleted";
+ private static final String MATCH_ALL_QUERY = "{" +
+ "\"combinator\": \"and\"," +
+ "\"criteria\": []" +
+ "}";
+
private MailboxId otherMailboxId;
protected abstract GuiceJamesServer createJmapServer() throws IOException;
@@ -416,6 +421,7 @@ public abstract class DeletedMessagesVaultTest {
private void restoreMessagesFor(String user) {
String taskId = webAdminApi.with()
+ .body(MATCH_ALL_QUERY)
.post("/deletedMessages/user/" + user + "?action=restore")
.jsonPath()
.get("taskId");
diff --git a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRestoreTask.java b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRestoreTask.java
index b6aa3e5..9d855b8 100644
--- a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRestoreTask.java
+++ b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRestoreTask.java
@@ -28,6 +28,7 @@ import org.apache.james.core.User;
import org.apache.james.mailbox.exception.MailboxException;
import org.apache.james.task.Task;
import org.apache.james.task.TaskExecutionDetails;
+import org.apache.james.vault.search.Query;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -72,8 +73,10 @@ class DeletedMessagesVaultRestoreTask implements Task {
private final User userToRestore;
private final RestoreService vaultRestore;
private final AdditionalInformation additionalInformation;
+ private final Query query;
- DeletedMessagesVaultRestoreTask(User userToRestore, RestoreService vaultRestore) {
+ DeletedMessagesVaultRestoreTask(RestoreService vaultRestore, User userToRestore, Query query) {
+ this.query = query;
this.userToRestore = userToRestore;
this.vaultRestore = vaultRestore;
this.additionalInformation = new AdditionalInformation(userToRestore);
@@ -82,7 +85,7 @@ class DeletedMessagesVaultRestoreTask implements Task {
@Override
public Result run() {
try {
- return vaultRestore.restore(userToRestore).toStream()
+ return vaultRestore.restore(userToRestore, query).toStream()
.peek(this::updateInformation)
.map(this::restoreResultToTaskResult)
.reduce(Task::combine)
diff --git a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutes.java b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutes.java
index edca31a..527b8d9 100644
--- a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutes.java
+++ b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutes.java
@@ -36,10 +36,16 @@ import org.apache.james.core.User;
import org.apache.james.task.Task;
import org.apache.james.task.TaskId;
import org.apache.james.task.TaskManager;
+import org.apache.james.vault.search.Query;
import org.apache.james.webadmin.Constants;
import org.apache.james.webadmin.Routes;
import org.apache.james.webadmin.dto.TaskIdDto;
+import org.apache.james.webadmin.utils.ErrorResponder;
+import org.apache.james.webadmin.utils.JsonExtractException;
+import org.apache.james.webadmin.utils.JsonExtractor;
import org.apache.james.webadmin.utils.JsonTransformer;
+import org.apache.james.webadmin.vault.routes.query.QueryElement;
+import org.apache.james.webadmin.vault.routes.query.QueryTranslator;
import org.eclipse.jetty.http.HttpStatus;
import com.github.steveash.guavate.Guavate;
@@ -53,6 +59,7 @@ import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
+import spark.HaltException;
import spark.Request;
import spark.Response;
import spark.Service;
@@ -99,14 +106,18 @@ public class DeletedMessagesVaultRoutes implements Routes {
private final RestoreService vaultRestore;
private final JsonTransformer jsonTransformer;
private final TaskManager taskManager;
+ private final JsonExtractor<QueryElement> jsonExtractor;
+ private final QueryTranslator queryTranslator;
@Inject
@VisibleForTesting
DeletedMessagesVaultRoutes(RestoreService vaultRestore, JsonTransformer jsonTransformer,
- TaskManager taskManager) {
+ TaskManager taskManager, QueryTranslator queryTranslator) {
this.vaultRestore = vaultRestore;
this.jsonTransformer = jsonTransformer;
this.taskManager = taskManager;
+ this.queryTranslator = queryTranslator;
+ this.jsonExtractor = new JsonExtractor<>(QueryElement.class);
}
@Override
@@ -145,7 +156,7 @@ public class DeletedMessagesVaultRoutes implements Routes {
@ApiResponse(code = HttpStatus.BAD_REQUEST_400, message = "Bad request - user param is invalid"),
@ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500, message = "Internal server error - Something went bad on the server side.")
})
- private TaskIdDto userActions(Request request, Response response) {
+ private TaskIdDto userActions(Request request, Response response) throws JsonExtractException {
UserVaultAction requestedAction = extractUserVaultAction(request);
Task requestedTask = generateTask(requestedAction, request);
@@ -153,17 +164,43 @@ public class DeletedMessagesVaultRoutes implements Routes {
return TaskIdDto.respond(response, taskId);
}
- private Task generateTask(UserVaultAction requestedAction, Request request) {
- User userToRestore = User.fromUsername(request.params(USER_PATH_PARAM));
+ private Task generateTask(UserVaultAction requestedAction, Request request) throws JsonExtractException {
+ User userToRestore = extractUser(request);
+ Query query = translate(jsonExtractor.parse(request.body()));
switch (requestedAction) {
case RESTORE:
- return new DeletedMessagesVaultRestoreTask(userToRestore, vaultRestore);
+ return new DeletedMessagesVaultRestoreTask(vaultRestore, userToRestore, query);
default:
throw new NotImplementedException(requestedAction + " is not yet handled.");
}
}
+ private Query translate(QueryElement queryElement) {
+ try {
+ return queryTranslator.translate(queryElement);
+ } catch (QueryTranslator.QueryTranslatorException e) {
+ throw badRequest("Invalid payload passing to the route", e);
+ }
+ }
+
+ private User extractUser(Request request) {
+ try {
+ return User.fromUsername(request.params(USER_PATH_PARAM));
+ } catch (IllegalArgumentException e) {
+ throw badRequest("Invalid 'user' parameter", e);
+ }
+ }
+
+ private HaltException badRequest(String message, Exception e) {
+ return ErrorResponder.builder()
+ .statusCode(HttpStatus.BAD_REQUEST_400)
+ .type(ErrorResponder.ErrorType.INVALID_ARGUMENT)
+ .message(message)
+ .cause(e)
+ .haltError();
+ }
+
private UserVaultAction extractUserVaultAction(Request request) {
String actionParam = request.queryParams(ACTION_QUERY_PARAM);
return Optional.ofNullable(actionParam)
diff --git a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/RestoreService.java b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/RestoreService.java
index f175c51..aa6efef 100644
--- a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/RestoreService.java
+++ b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/RestoreService.java
@@ -65,11 +65,11 @@ class RestoreService {
this.mailboxManager = mailboxManager;
}
- Flux<RestoreResult> restore(User userToRestore) throws MailboxException {
+ Flux<RestoreResult> restore(User userToRestore, Query searchQuery) throws MailboxException {
MailboxSession session = mailboxManager.createSystemSession(userToRestore.asString());
MessageManager restoreMessageManager = restoreMailboxManager(session);
- return Flux.from(deletedMessageVault.search(userToRestore, Query.ALL))
+ return Flux.from(deletedMessageVault.search(userToRestore, searchQuery))
.flatMap(deletedMessage -> appendToMailbox(restoreMessageManager, deletedMessage, session));
}
diff --git a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/query/QueryElement.java b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/query/QueryElement.java
index 2c0e9e1..1f258ba 100644
--- a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/query/QueryElement.java
+++ b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/query/QueryElement.java
@@ -22,5 +22,5 @@ package org.apache.james.webadmin.vault.routes.query;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
@JsonDeserialize(using = QueryElementDeserializer.class)
-interface QueryElement {
+public interface QueryElement {
}
diff --git a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/query/QueryTranslator.java b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/query/QueryTranslator.java
index 33067d6..611c2a4 100644
--- a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/query/QueryTranslator.java
+++ b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/query/QueryTranslator.java
@@ -38,6 +38,7 @@ import static org.apache.james.webadmin.vault.routes.query.QueryTranslator.Opera
import static org.apache.james.webadmin.vault.routes.query.QueryTranslator.Operator.EQUALS_IGNORE_CASE;
import java.time.ZonedDateTime;
+import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;
@@ -205,18 +206,28 @@ public class QueryTranslator {
Operator.getOperator(dto.getOperator()));
}
- public Query translate(QueryDTO queryDTO) throws QueryTranslatorException {
- Preconditions.checkArgument(isAndCombinator(queryDTO.getCombinator()), "combinator '" + queryDTO.getCombinator() + "' is not yet handled");
+ public Query translate(QueryElement queryElement) throws QueryTranslatorException {
+ if (queryElement instanceof QueryDTO) {
+ return translate((QueryDTO) queryElement);
+ } else if (queryElement instanceof CriterionDTO) {
+ return Query.of(translate((CriterionDTO) queryElement));
+ }
+ throw new IllegalArgumentException("cannot resolve query type: " + queryElement.getClass().getName());
+ }
+
+ Query translate(QueryDTO queryDTO) throws QueryTranslatorException {
+ Preconditions.checkArgument(combinatorIsValid(queryDTO.getCombinator()), "combinator '" + queryDTO.getCombinator() + "' is not yet handled");
Preconditions.checkArgument(queryDTO.getCriteria().stream().allMatch(this::isCriterion), "nested query structure is not yet handled");
- return Query.of(queryDTO.getCriteria().stream()
+ return Query.and(queryDTO.getCriteria().stream()
.map(queryElement -> (CriterionDTO) queryElement)
.map(Throwing.function(this::translate))
.collect(Guavate.toImmutableList()));
}
- private boolean isAndCombinator(String combinator) {
- return Combinator.AND.getValue().equals(combinator);
+ private boolean combinatorIsValid(String combinator) {
+ return Combinator.AND.getValue().equals(combinator)
+ || Objects.isNull(combinator);
}
private boolean isCriterion(QueryElement queryElement) {
diff --git a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutesTest.java b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutesTest.java
index 460e3c2..1afa30d 100644
--- a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutesTest.java
+++ b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutesTest.java
@@ -21,14 +21,25 @@ package org.apache.james.webadmin.vault.routes;
import static io.restassured.RestAssured.given;
import static io.restassured.RestAssured.when;
-import static io.restassured.RestAssured.with;
import static org.apache.james.vault.DeletedMessageFixture.CONTENT;
import static org.apache.james.vault.DeletedMessageFixture.DELETED_MESSAGE;
import static org.apache.james.vault.DeletedMessageFixture.DELETED_MESSAGE_2;
+import static org.apache.james.vault.DeletedMessageFixture.DELETION_DATE;
+import static org.apache.james.vault.DeletedMessageFixture.DELIVERY_DATE;
+import static org.apache.james.vault.DeletedMessageFixture.FINAL_STAGE;
+import static org.apache.james.vault.DeletedMessageFixture.MAILBOX_ID_1;
+import static org.apache.james.vault.DeletedMessageFixture.MAILBOX_ID_3;
+import static org.apache.james.vault.DeletedMessageFixture.SUBJECT;
import static org.apache.james.vault.DeletedMessageFixture.USER;
import static org.apache.james.vault.DeletedMessageFixture.USER_2;
+import static org.apache.james.vault.DeletedMessageVaultSearchContract.MESSAGE_ID_GENERATOR;
import static org.apache.james.webadmin.WebAdminServer.NO_CONFIGURATION;
import static org.apache.james.webadmin.vault.routes.RestoreService.RESTORE_MAILBOX_NAME;
+import static org.apache.mailet.base.MailAddressFixture.RECIPIENT1;
+import static org.apache.mailet.base.MailAddressFixture.RECIPIENT2;
+import static org.apache.mailet.base.MailAddressFixture.RECIPIENT3;
+import static org.apache.mailet.base.MailAddressFixture.SENDER;
+import static org.apache.mailet.base.MailAddressFixture.SENDER2;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
@@ -40,13 +51,17 @@ import static org.mockito.Mockito.spy;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.List;
+import java.util.stream.Stream;
+import org.apache.james.core.MaybeSender;
import org.apache.james.core.User;
import org.apache.james.mailbox.MailboxSession;
import org.apache.james.mailbox.MessageManager;
import org.apache.james.mailbox.acl.SimpleGroupMembershipResolver;
import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.mailbox.inmemory.InMemoryId;
import org.apache.james.mailbox.inmemory.InMemoryMailboxManager;
+import org.apache.james.mailbox.inmemory.InMemoryMessageId;
import org.apache.james.mailbox.inmemory.manager.InMemoryIntegrationResources;
import org.apache.james.mailbox.model.FetchGroupImpl;
import org.apache.james.mailbox.model.MailboxId;
@@ -57,6 +72,7 @@ import org.apache.james.mailbox.model.MultimailboxesSearchQuery;
import org.apache.james.mailbox.model.SearchQuery;
import org.apache.james.metrics.logger.DefaultMetricFactory;
import org.apache.james.task.MemoryTaskManager;
+import org.apache.james.vault.DeletedMessage;
import org.apache.james.vault.memory.MemoryDeletedMessagesVault;
import org.apache.james.vault.search.Query;
import org.apache.james.webadmin.WebAdminServer;
@@ -64,6 +80,7 @@ import org.apache.james.webadmin.WebAdminUtils;
import org.apache.james.webadmin.routes.TasksRoutes;
import org.apache.james.webadmin.utils.ErrorResponder;
import org.apache.james.webadmin.utils.JsonTransformer;
+import org.apache.james.webadmin.vault.routes.query.QueryTranslator;
import org.eclipse.jetty.http.HttpStatus;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@@ -76,12 +93,19 @@ import com.google.common.collect.ImmutableList;
import io.restassured.RestAssured;
import io.restassured.filter.log.LogDetail;
import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
class DeletedMessagesVaultRoutesTest {
+ private static final String MATCH_ALL_QUERY = "{" +
+ "\"combinator\": \"and\"," +
+ "\"criteria\": []" +
+ "}";
+
private WebAdminServer webAdminServer;
private MemoryDeletedMessagesVault vault;
private InMemoryMailboxManager mailboxManager;
+ private MemoryTaskManager taskManager;
@BeforeEach
void beforeEach() throws Exception {
@@ -90,14 +114,15 @@ class DeletedMessagesVaultRoutesTest {
InMemoryIntegrationResources.Resources inMemoryResource = inMemoryIntegrationResources.createResources(new SimpleGroupMembershipResolver());
mailboxManager = spy(inMemoryResource.getMailboxManager());
- MemoryTaskManager taskManager = new MemoryTaskManager();
+ taskManager = new MemoryTaskManager();
JsonTransformer jsonTransformer = new JsonTransformer();
RestoreService vaultRestore = new RestoreService(vault, mailboxManager);
+ QueryTranslator queryTranslator = new QueryTranslator(new InMemoryId.Factory());
webAdminServer = WebAdminUtils.createWebAdminServer(
new DefaultMetricFactory(),
new TasksRoutes(taskManager, jsonTransformer),
- new DeletedMessagesVaultRoutes(vaultRestore, jsonTransformer, taskManager));
+ new DeletedMessagesVaultRoutes(vaultRestore, jsonTransformer, taskManager, queryTranslator));
webAdminServer.configure(NO_CONFIGURATION);
webAdminServer.await();
@@ -110,6 +135,947 @@ class DeletedMessagesVaultRoutesTest {
@AfterEach
void afterEach() {
webAdminServer.destroy();
+ taskManager.stop();
+ }
+
+ @Nested
+ class QueryTest {
+
+ @Nested
+ class SubjectTest {
+
+ @Test
+ void restoreShouldAppendMessageToMailboxWhenMatchingSubjectContains() throws Exception {
+ vault.append(USER, FINAL_STAGE.get()
+ .subject("subject contains should match")
+ .build(), new ByteArrayInputStream(CONTENT)).block();
+
+ String query =
+ "{" +
+ " \"fieldName\": \"subject\"," +
+ " \"operator\": \"contains\"," +
+ " \"value\": \"subject contains\"" +
+ "}";
+
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
+
+ given()
+ .basePath(TasksRoutes.BASE)
+ .when()
+ .get(taskId + "/await")
+ .then()
+ .body("status", is("completed"));
+
+ assertThat(restoreMessageContents(USER))
+ .hasSize(1)
+ .hasOnlyOneElementSatisfying(messageIS -> assertThat(messageIS).hasSameContentAs(new ByteArrayInputStream(CONTENT)));
+ }
+
+ @Test
+ void restoreShouldNotAppendMessageToMailboxWhenSubjectDoesntContains() throws Exception {
+ vault.append(USER, FINAL_STAGE.get()
+ .subject("subject")
+ .build(), new ByteArrayInputStream(CONTENT)).block();
+
+ String query =
+ "{" +
+ " \"fieldName\": \"subject\"," +
+ " \"operator\": \"contains\"," +
+ " \"value\": \"james\"" +
+ "}";
+
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
+
+ given()
+ .basePath(TasksRoutes.BASE)
+ .when()
+ .get(taskId + "/await")
+ .then()
+ .body("status", is("completed"));
+
+ assertThat(hasAnyMail(USER)).isFalse();
+ }
+
+ @Test
+ void restoreShouldAppendMessageToMailboxWhenMatchingSubjectContainsIgnoreCase() throws Exception {
+ vault.append(USER, FINAL_STAGE.get()
+ .subject("SUBJECT contains should match")
+ .build(), new ByteArrayInputStream(CONTENT)).block();
+
+ String query =
+ "{" +
+ " \"fieldName\": \"subject\"," +
+ " \"operator\": \"containsIgnoreCase\"," +
+ " \"value\": \"subject contains\"" +
+ "}";
+
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
+
+ given()
+ .basePath(TasksRoutes.BASE)
+ .when()
+ .get(taskId + "/await")
+ .then()
+ .body("status", is("completed"));
+
+ assertThat(restoreMessageContents(USER))
+ .hasSize(1)
+ .hasOnlyOneElementSatisfying(messageIS -> assertThat(messageIS).hasSameContentAs(new ByteArrayInputStream(CONTENT)));
+ }
+
+ @Test
+ void restoreShouldNotAppendMessageToMailboxWhenSubjectDoesntContainsIgnoreCase() throws Exception {
+ vault.append(USER, FINAL_STAGE.get()
+ .subject("subject")
+ .build(), new ByteArrayInputStream(CONTENT)).block();
+
+ String query =
+ "{" +
+ " \"fieldName\": \"subject\"," +
+ " \"operator\": \"containsIgnoreCase\"," +
+ " \"value\": \"JAMES\"" +
+ "}";
+
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
+
+ given()
+ .basePath(TasksRoutes.BASE)
+ .when()
+ .get(taskId + "/await")
+ .then()
+ .body("status", is("completed"));
+
+ assertThat(hasAnyMail(USER)).isFalse();
+ }
+
+ @Test
+ void restoreShouldAppendMessageToMailboxWhenMatchingSubjectEquals() throws Exception {
+ vault.append(USER, FINAL_STAGE.get()
+ .subject("subject should match")
+ .build(), new ByteArrayInputStream(CONTENT)).block();
+
+ String query =
+ "{" +
+ " \"fieldName\": \"subject\"," +
+ " \"operator\": \"equals\"," +
+ " \"value\": \"subject should match\"" +
+ "}";
+
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
+
+ given()
+ .basePath(TasksRoutes.BASE)
+ .when()
+ .get(taskId + "/await")
+ .then()
+ .body("status", is("completed"));
+
+ assertThat(restoreMessageContents(USER))
+ .hasSize(1)
+ .hasOnlyOneElementSatisfying(messageIS -> assertThat(messageIS).hasSameContentAs(new ByteArrayInputStream(CONTENT)));
+ }
+
+ @Test
+ void restoreShouldNotAppendMessageToMailboxWhenSubjectDoesntEquals() throws Exception {
+ vault.append(USER, FINAL_STAGE.get()
+ .subject("subject")
+ .build(), new ByteArrayInputStream(CONTENT)).block();
+
+ String query =
+ "{" +
+ " \"fieldName\": \"subject\"," +
+ " \"operator\": \"equals\"," +
+ " \"value\": \"SUBJECT\"" +
+ "}";
+
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
+
+ given()
+ .basePath(TasksRoutes.BASE)
+ .when()
+ .get(taskId + "/await")
+ .then()
+ .body("status", is("completed"));
+
+ assertThat(hasAnyMail(USER)).isFalse();
+ }
+
+ @Test
+ void restoreShouldAppendMessageToMailboxWhenMatchingSubjectEqualsIgnoreCase() throws Exception {
+ vault.append(USER, FINAL_STAGE.get()
+ .subject("SUBJECT should MatCH")
+ .build(), new ByteArrayInputStream(CONTENT)).block();
+
+ String query =
+ "{" +
+ " \"fieldName\": \"subject\"," +
+ " \"operator\": \"equalsIgnoreCase\"," +
+ " \"value\": \"subject should match\"" +
+ "}";
+
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
+
+ given()
+ .basePath(TasksRoutes.BASE)
+ .when()
+ .get(taskId + "/await")
+ .then()
+ .body("status", is("completed"));
+
+ assertThat(restoreMessageContents(USER))
+ .hasSize(1)
+ .hasOnlyOneElementSatisfying(messageIS -> assertThat(messageIS).hasSameContentAs(new ByteArrayInputStream(CONTENT)));
+ }
+
+ @Test
+ void restoreShouldNotAppendMessageToMailboxWhenSubjectDoesntEqualsIgnoreCase() throws Exception {
+ vault.append(USER, FINAL_STAGE.get()
+ .subject("subject")
+ .build(), new ByteArrayInputStream(CONTENT)).block();
+
+ String query =
+ "{" +
+ " \"fieldName\": \"subject\"," +
+ " \"operator\": \"equalsIgnoreCase\"," +
+ " \"value\": \"SUBJECT Of the mail\"" +
+ "}";
+
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
+
+ given()
+ .basePath(TasksRoutes.BASE)
+ .when()
+ .get(taskId + "/await")
+ .then()
+ .body("status", is("completed"));
+
+ assertThat(hasAnyMail(USER)).isFalse();
+ }
+ }
+
+ @Nested
+ class DeletionDateTest {
+
+ @Test
+ void restoreShouldAppendMessageToMailboxWhenMatchingDeletionDateBeforeOrEquals() throws Exception {
+ vault.append(USER, DELETED_MESSAGE, new ByteArrayInputStream(CONTENT)).block();
+
+ String query =
+ "{" +
+ " \"fieldName\": \"deletionDate\"," +
+ " \"operator\": \"beforeOrEquals\"," +
+ " \"value\": \"" + DELETION_DATE.plusHours(1).toString() + "\"" +
+ "}";
+
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
+
+ given()
+ .basePath(TasksRoutes.BASE)
+ .when()
+ .get(taskId + "/await")
+ .then()
+ .body("status", is("completed"));
+
+ assertThat(restoreMessageContents(USER))
+ .hasSize(1)
+ .hasOnlyOneElementSatisfying(messageIS -> assertThat(messageIS).hasSameContentAs(new ByteArrayInputStream(CONTENT)));
+ }
+
+ @Test
+ void restoreShouldNotAppendMessageToMailboxWhenNotMatchingDeletionDateBeforeOrEquals() throws Exception {
+ vault.append(USER, DELETED_MESSAGE, new ByteArrayInputStream(CONTENT)).block();
+
+ String query =
+ "{" +
+ " \"fieldName\": \"deletionDate\"," +
+ " \"operator\": \"beforeOrEquals\"," +
+ " \"value\": \"" + DELETION_DATE.minusHours(1).toString() + "\"" +
+ "}";
+
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
+
+ given()
+ .basePath(TasksRoutes.BASE)
+ .when()
+ .get(taskId + "/await")
+ .then()
+ .body("status", is("completed"));
+
+ assertThat(hasAnyMail(USER)).isFalse();
+ }
+
+ @Test
+ void restoreShouldAppendMessageToMailboxWhenMatchingDeletionDateAfterOrEquals() throws Exception {
+ vault.append(USER, DELETED_MESSAGE, new ByteArrayInputStream(CONTENT)).block();
+
+ String query =
+ "{" +
+ " \"fieldName\": \"deletionDate\"," +
+ " \"operator\": \"afterOrEquals\"," +
+ " \"value\": \"" + DELETION_DATE.minusHours(1).toString() + "\"" +
+ "}";
+
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
+
+ given()
+ .basePath(TasksRoutes.BASE)
+ .when()
+ .get(taskId + "/await")
+ .then()
+ .body("status", is("completed"));
+
+ assertThat(restoreMessageContents(USER))
+ .hasSize(1)
+ .hasOnlyOneElementSatisfying(messageIS -> assertThat(messageIS).hasSameContentAs(new ByteArrayInputStream(CONTENT)));
+ }
+
+ @Test
+ void restoreShouldNotAppendMessageToMailboxWhenNotMatchingDeletionDateAfterOrEquals() throws Exception {
+ vault.append(USER, DELETED_MESSAGE, new ByteArrayInputStream(CONTENT)).block();
+
+ String query =
+ "{" +
+ " \"fieldName\": \"deletionDate\"," +
+ " \"operator\": \"afterOrEquals\"," +
+ " \"value\": \"" + DELETION_DATE.plusHours(1).toString() + "\"" +
+ "}";
+
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
+
+ given()
+ .basePath(TasksRoutes.BASE)
+ .when()
+ .get(taskId + "/await")
+ .then()
+ .body("status", is("completed"));
+
+ assertThat(hasAnyMail(USER)).isFalse();
+ }
+ }
+
+ @Nested
+ class DeliveryDateTest {
+
+ @Test
+ void restoreShouldAppendMessageToMailboxWhenMatchingDeliveryDateBeforeOrEquals() throws Exception {
+ vault.append(USER, DELETED_MESSAGE, new ByteArrayInputStream(CONTENT)).block();
+
+ String query =
+ "{" +
+ " \"fieldName\": \"deliveryDate\"," +
+ " \"operator\": \"beforeOrEquals\"," +
+ " \"value\": \"" + DELIVERY_DATE.plusHours(1).toString() + "\"" +
+ "}";
+
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
+
+ given()
+ .basePath(TasksRoutes.BASE)
+ .when()
+ .get(taskId + "/await")
+ .then()
+ .body("status", is("completed"));
+
+ assertThat(restoreMessageContents(USER))
+ .hasSize(1)
+ .hasOnlyOneElementSatisfying(messageIS -> assertThat(messageIS).hasSameContentAs(new ByteArrayInputStream(CONTENT)));
+ }
+
+ @Test
+ void restoreShouldNotAppendMessageToMailboxWhenNotMatchingDeliveryDateBeforeOrEquals() throws Exception {
+ vault.append(USER, DELETED_MESSAGE, new ByteArrayInputStream(CONTENT)).block();
+
+ String query =
+ "{" +
+ " \"fieldName\": \"deliveryDate\"," +
+ " \"operator\": \"beforeOrEquals\"," +
+ " \"value\": \"" + DELIVERY_DATE.minusHours(1).toString() + "\"" +
+ "}";
+
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
+
+ given()
+ .basePath(TasksRoutes.BASE)
+ .when()
+ .get(taskId + "/await")
+ .then()
+ .body("status", is("completed"));
+
+ assertThat(hasAnyMail(USER)).isFalse();
+ }
+
+ @Test
+ void restoreShouldAppendMessageToMailboxWhenMatchingDeliveryDateAfterOrEquals() throws Exception {
+ vault.append(USER, DELETED_MESSAGE, new ByteArrayInputStream(CONTENT)).block();
+
+ String query =
+ "{" +
+ " \"fieldName\": \"deliveryDate\"," +
+ " \"operator\": \"afterOrEquals\"," +
+ " \"value\": \"" + DELIVERY_DATE.minusHours(1).toString() + "\"" +
+ "}";
+
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
+
+ given()
+ .basePath(TasksRoutes.BASE)
+ .when()
+ .get(taskId + "/await")
+ .then()
+ .body("status", is("completed"));
+
+ assertThat(restoreMessageContents(USER))
+ .hasSize(1)
+ .hasOnlyOneElementSatisfying(messageIS -> assertThat(messageIS).hasSameContentAs(new ByteArrayInputStream(CONTENT)));
+ }
+
+ @Test
+ void restoreShouldNotAppendMessageToMailboxWhenNotMatchingDeliveryDateAfterOrEquals() throws Exception {
+ vault.append(USER, DELETED_MESSAGE, new ByteArrayInputStream(CONTENT)).block();
+
+ String query =
+ "{" +
+ " \"fieldName\": \"deliveryDate\"," +
+ " \"operator\": \"afterOrEquals\"," +
+ " \"value\": \"" + DELIVERY_DATE.plusHours(1).toString() + "\"" +
+ "}";
+
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
+
+ given()
+ .basePath(TasksRoutes.BASE)
+ .when()
+ .get(taskId + "/await")
+ .then()
+ .body("status", is("completed"));
+
+ assertThat(hasAnyMail(USER)).isFalse();
+ }
+ }
+
+ @Nested
+ class RecipientsTest {
+
+ @Test
+ void restoreShouldAppendMessageToMailboxWhenMatchingRecipientContains() throws Exception {
+ vault.append(USER, DELETED_MESSAGE, new ByteArrayInputStream(CONTENT)).block();
+
+ String query =
+ "{" +
+ " \"fieldName\": \"recipients\"," +
+ " \"operator\": \"contains\"," +
+ " \"value\": \"" + RECIPIENT1.asString() + "\"" +
+ "}";
+
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
+
+ given()
+ .basePath(TasksRoutes.BASE)
+ .when()
+ .get(taskId + "/await")
+ .then()
+ .body("status", is("completed"));
+
+ assertThat(restoreMessageContents(USER))
+ .hasSize(1)
+ .hasOnlyOneElementSatisfying(messageIS -> assertThat(messageIS).hasSameContentAs(new ByteArrayInputStream(CONTENT)));
+ }
+
+ @Test
+ void restoreShouldNotAppendMessageToMailboxWhenMatchingRecipientsDoNotContain() throws Exception {
+ vault.append(USER, DELETED_MESSAGE, new ByteArrayInputStream(CONTENT)).block();
+
+ String query =
+ "{" +
+ " \"fieldName\": \"recipients\"," +
+ " \"operator\": \"contains\"," +
+ " \"value\": \"" + RECIPIENT3.asString() + "\"" +
+ "}";
+
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
+
+ given()
+ .basePath(TasksRoutes.BASE)
+ .when()
+ .get(taskId + "/await")
+ .then()
+ .body("status", is("completed"));
+
+ assertThat(hasAnyMail(USER)).isFalse();
+ }
+ }
+
+ @Nested
+ class SenderTest {
+ @Test
+ void restoreShouldAppendMessageToMailboxWhenMatchingSenderEquals() throws Exception {
+ vault.append(USER, DELETED_MESSAGE, new ByteArrayInputStream(CONTENT)).block();
+
+ String query =
+ "{" +
+ " \"fieldName\": \"sender\"," +
+ " \"operator\": \"equals\"," +
+ " \"value\": \"" + SENDER.asString() + "\"" +
+ "}";
+
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
+
+ given()
+ .basePath(TasksRoutes.BASE)
+ .when()
+ .get(taskId + "/await")
+ .then()
+ .body("status", is("completed"));
+
+ assertThat(restoreMessageContents(USER))
+ .hasSize(1)
+ .hasOnlyOneElementSatisfying(messageIS -> assertThat(messageIS).hasSameContentAs(new ByteArrayInputStream(CONTENT)));
+ }
+
+ @Test
+ void restoreShouldAppendMessageToMailboxWhenMatchingSenderDoesntEquals() throws Exception {
+ vault.append(USER, DELETED_MESSAGE, new ByteArrayInputStream(CONTENT)).block();
+
+ String query =
+ "{" +
+ " \"fieldName\": \"sender\"," +
+ " \"operator\": \"equals\"," +
+ " \"value\": \"" + SENDER2.asString() + "\"" +
+ "}";
+
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
+
+ given()
+ .basePath(TasksRoutes.BASE)
+ .when()
+ .get(taskId + "/await")
+ .then()
+ .body("status", is("completed"));
+
+ assertThat(hasAnyMail(USER)).isFalse();
+ }
+ }
+
+ @Nested
+ class HasAttachmentTest {
+
+ @Test
+ void restoreShouldAppendMessageToMailboxWhenMatchingNoAttachment() throws Exception {
+ DeletedMessage deletedMessage = messageWithAttachmentBuilder()
+ .hasAttachment(false)
+ .build();
+ storeDeletedMessage(deletedMessage);
+
+ String query =
+ "{" +
+ " \"fieldName\": \"hasAttachment\"," +
+ " \"operator\": \"equals\"," +
+ " \"value\": \"false\"" +
+ "}";
+
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
+
+ given()
+ .basePath(TasksRoutes.BASE)
+ .when()
+ .get(taskId + "/await")
+ .then()
+ .body("status", is("completed"));
+
+ assertThat(restoreMessageContents(USER))
+ .hasSize(1)
+ .hasOnlyOneElementSatisfying(messageIS -> assertThat(messageIS).hasSameContentAs(new ByteArrayInputStream(CONTENT)));
+ }
+
+ @Test
+ void restoreShouldAppendMessageToMailboxWhenMatchingHasAttachment() throws Exception {
+ DeletedMessage deletedMessage = messageWithAttachmentBuilder()
+ .hasAttachment()
+ .build();
+ storeDeletedMessage(deletedMessage);
+
+ String query =
+ " {" +
+ " \"fieldName\": \"hasAttachment\"," +
+ " \"operator\": \"equals\"," +
+ " \"value\": \"true\"" +
+ "}";
+
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
+
+ given()
+ .basePath(TasksRoutes.BASE)
+ .when()
+ .get(taskId + "/await")
+ .then()
+ .body("status", is("completed"));
+
+ assertThat(restoreMessageContents(USER))
+ .hasSize(1)
+ .hasOnlyOneElementSatisfying(messageIS -> assertThat(messageIS).hasSameContentAs(new ByteArrayInputStream(CONTENT)));
+ }
+
+ @Test
+ void restoreShouldNotAppendMessageToMailboxWhenMatchingHasNoAttachment() throws Exception {
+ DeletedMessage deletedMessage = messageWithAttachmentBuilder()
+ .hasAttachment(false)
+ .build();
+ storeDeletedMessage(deletedMessage);
+
+ String query =
+ "{" +
+ " \"fieldName\": \"hasAttachment\"," +
+ " \"operator\": \"equals\"," +
+ " \"value\": \"true\"" +
+ "}";
+
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
+
+ given()
+ .basePath(TasksRoutes.BASE)
+ .when()
+ .get(taskId + "/await")
+ .then()
+ .body("status", is("completed"));
+
+ assertThat(hasAnyMail(USER)).isFalse();
+ }
+ }
+
+ @Nested
+ class OriginMailboxIdsTest {
+
+ @Test
+ void restoreShouldAppendMessageToMailboxWhenContainsMailboxId() throws Exception {
+ vault.append(USER, DELETED_MESSAGE, new ByteArrayInputStream(CONTENT)).block();
+
+ String query =
+ "{" +
+ " \"fieldName\": \"originMailboxes\"," +
+ " \"operator\": \"contains\"," +
+ " \"value\": \"" + MAILBOX_ID_1.serialize() + "\"" +
+ "}";
+
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
+
+ given()
+ .basePath(TasksRoutes.BASE)
+ .when()
+ .get(taskId + "/await")
+ .then()
+ .body("status", is("completed"));
+
+ assertThat(restoreMessageContents(USER))
+ .hasSize(1)
+ .hasOnlyOneElementSatisfying(messageIS -> assertThat(messageIS).hasSameContentAs(new ByteArrayInputStream(CONTENT)));
+ }
+
+ @Test
+ void restoreShouldNotAppendMessageToMailboxWhenDoNotContainsMailboxId() throws Exception {
+ vault.append(USER, DELETED_MESSAGE, new ByteArrayInputStream(CONTENT)).block();
+
+ String query =
+ "{" +
+ " \"fieldName\": \"originMailboxes\"," +
+ " \"operator\": \"contains\"," +
+ " \"value\": \"" + MAILBOX_ID_3.serialize() + "\"" +
+ "}";
+
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
+
+ given()
+ .basePath(TasksRoutes.BASE)
+ .when()
+ .get(taskId + "/await")
+ .then()
+ .body("status", is("completed"));
+
+ assertThat(hasAnyMail(USER)).isFalse();
+ }
+ }
+
+ @Nested
+ class MultipleCriteriaTest {
+
+ @Test
+ void restoreShouldAppendMessageToMailboxWhenAllcriteriaAreMatched() throws Exception {
+ vault.append(USER, DELETED_MESSAGE, new ByteArrayInputStream(CONTENT)).block();
+ vault.append(USER, DELETED_MESSAGE_2, new ByteArrayInputStream(CONTENT)).block();
+
+ String query = "" +
+ "{" +
+ " \"combinator\": \"and\"," +
+ " \"criteria\": [" +
+ " {" +
+ " \"fieldName\": \"deliveryDate\"," +
+ " \"operator\": \"beforeOrEquals\"," +
+ " \"value\": \"" + DELIVERY_DATE.toString() + "\"" +
+ " }," +
+ " {" +
+ " \"fieldName\": \"recipients\"," +
+ " \"operator\": \"contains\"," +
+ " \"value\": \"" + RECIPIENT1.asString() + "\"" +
+ " }," +
+ " {" +
+ " \"fieldName\": \"hasAttachment\"," +
+ " \"operator\": \"equals\"," +
+ " \"value\": \"false\"" +
+ " }," +
+ " {" +
+ " \"fieldName\": \"originMailboxes\"," +
+ " \"operator\": \"contains\"," +
+ " \"value\": \"" + MAILBOX_ID_1.serialize() + "\"" +
+ " }" +
+ " ]" +
+ "}";
+
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
+
+ given()
+ .basePath(TasksRoutes.BASE)
+ .when()
+ .get(taskId + "/await")
+ .then()
+ .body("status", is("completed"));
+
+ assertThat(restoreMessageContents(USER))
+ .hasSize(2)
+ .allSatisfy(messageIS -> assertThat(messageIS).hasSameContentAs(new ByteArrayInputStream(CONTENT)));
+ }
+
+ @Test
+ void restoreShouldNotAppendMessageToMailboxWhenASingleCriterionDoesntMatch() throws Exception {
+ vault.append(USER, DELETED_MESSAGE, new ByteArrayInputStream(CONTENT)).block();
+ vault.append(USER, DELETED_MESSAGE_2, new ByteArrayInputStream(CONTENT)).block();
+
+ String query = "" +
+ "{" +
+ " \"combinator\": \"and\"," +
+ " \"criteria\": [" +
+ " {" +
+ " \"fieldName\": \"deliveryDate\"," +
+ " \"operator\": \"beforeOrEquals\"," +
+ " \"value\": \"" + DELIVERY_DATE.toString() + "\"" +
+ " }," +
+ " {" +
+ " \"fieldName\": \"recipients\"," +
+ " \"operator\": \"contains\"," +
+ " \"value\": \"allMessageDoNotHaveThisRecipient@domain.tld\"" +
+ " }," +
+ " {" +
+ " \"fieldName\": \"hasAttachment\"," +
+ " \"operator\": \"equals\"," +
+ " \"value\": \"false\"" +
+ " }," +
+ " {" +
+ " \"fieldName\": \"originMailboxes\"," +
+ " \"operator\": \"contains\"," +
+ " \"value\": \"" + MAILBOX_ID_1.serialize() + "\"" +
+ " }" +
+ " ]" +
+ "}";
+
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
+
+ given()
+ .basePath(TasksRoutes.BASE)
+ .when()
+ .get(taskId + "/await")
+ .then()
+ .body("status", is("completed"));
+
+ assertThat(hasAnyMail(USER)).isFalse();
+ }
+ }
}
@Nested
@@ -192,8 +1158,175 @@ class DeletedMessagesVaultRoutesTest {
.then()
.statusCode(HttpStatus.NOT_FOUND_404)
.body("statusCode", is(404))
- .body("type", is(ErrorResponder.ErrorType.NOT_FOUND.getType()))
- .body("message", is("POST /deletedMessages/user can not be found"));
+ .body("type", is(notNullValue()))
+ .body("message", is(notNullValue()));
+ }
+
+ @Test
+ void restoreShouldReturnBadRequestWhenPassingUnsupportedField() throws Exception {
+ vault.append(USER, DELETED_MESSAGE, new ByteArrayInputStream(CONTENT)).block();
+
+ String query =
+ "{" +
+ " \"criteria\": [" +
+ " {" +
+ " \"fieldName\": \"unsupported\"," +
+ " \"operator\": \"contains\"," +
+ " \"value\": \"" + MAILBOX_ID_1.serialize() + "\"" +
+ " }" +
+ " ]" +
+ "}";
+
+ given()
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .then()
+ .statusCode(HttpStatus.BAD_REQUEST_400)
+ .body("statusCode", is(400))
+ .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType()))
+ .body("message", is(notNullValue()))
+ .body("details", is(notNullValue()));
+ }
+
+ @Test
+ void restoreShouldReturnBadRequestWhenPassingUnsupportedOperator() throws Exception {
+ vault.append(USER, DELETED_MESSAGE, new ByteArrayInputStream(CONTENT)).block();
+
+ String query =
+ "{" +
+ " \"criteria\": [" +
+ " {" +
+ " \"fieldName\": \"subject\"," +
+ " \"operator\": \"isLongerThan\"," +
+ " \"value\": \"" + SUBJECT + "\"" +
+ " }" +
+ " ]" +
+ "}";
+
+ given()
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .then()
+ .statusCode(HttpStatus.BAD_REQUEST_400)
+ .body("statusCode", is(400))
+ .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType()))
+ .body("message", is(notNullValue()))
+ .body("details", is(notNullValue()));
+ }
+
+ @Test
+ void restoreShouldReturnBadRequestWhenPassingUnsupportedPairOfFieldNameAndOperator() throws Exception {
+ vault.append(USER, DELETED_MESSAGE, new ByteArrayInputStream(CONTENT)).block();
+
+ String query =
+ "{" +
+ " \"criteria\": [" +
+ " {" +
+ " \"fieldName\": \"sender\"," +
+ " \"operator\": \"contains\"," +
+ " \"value\": \"" + SENDER.asString() + "\"" +
+ " }" +
+ " ]" +
+ "}";
+
+ given()
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .then()
+ .statusCode(HttpStatus.BAD_REQUEST_400)
+ .body("statusCode", is(400))
+ .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType()))
+ .body("message", is(notNullValue()))
+ .body("details", is(notNullValue()));
+ }
+
+ @Test
+ void restoreShouldReturnBadRequestWhenPassingInvalidMailAddress() throws Exception {
+ vault.append(USER, DELETED_MESSAGE, new ByteArrayInputStream(CONTENT)).block();
+
+ String query =
+ "{" +
+ " \"criteria\": [" +
+ " {" +
+ " \"fieldName\": \"sender\"," +
+ " \"operator\": \"contains\"," +
+ " \"value\": \"invalid@mail@domain.tld\"" +
+ " }" +
+ " ]" +
+ "}";
+
+ given()
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .then()
+ .statusCode(HttpStatus.BAD_REQUEST_400)
+ .body("statusCode", is(400))
+ .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType()))
+ .body("message", is(notNullValue()))
+ .body("details", is(notNullValue()));
+ }
+
+ @Test
+ void restoreShouldReturnBadRequestWhenPassingOrCombinator() throws Exception {
+ vault.append(USER, DELETED_MESSAGE, new ByteArrayInputStream(CONTENT)).block();
+
+ String query =
+ "{" +
+ " \"combinator\": \"or\"," +
+ " \"criteria\": [" +
+ " {" +
+ " \"fieldName\": \"sender\"," +
+ " \"operator\": \"contains\"," +
+ " \"value\": \"" + SENDER.asString() + "\"" +
+ " }" +
+ " ]" +
+ "}";
+
+ given()
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .then()
+ .statusCode(HttpStatus.BAD_REQUEST_400)
+ .body("statusCode", is(400))
+ .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType()))
+ .body("message", is(notNullValue()))
+ .body("details", is(notNullValue()));
+ }
+
+ @Test
+ void restoreShouldReturnBadRequestWhenPassingNestedStructuredQuery() throws Exception {
+ vault.append(USER, DELETED_MESSAGE, new ByteArrayInputStream(CONTENT)).block();
+
+ String query =
+ "{" +
+ " \"combinator\": \"and\"," +
+ " \"criteria\": [" +
+ " {" +
+ " \"combinator\": \"or\"," +
+ " \"criteria\": [" +
+ " {\"fieldName\": \"subject\", \"operator\": \"containsIgnoreCase\", \"value\": \"Apache James\"}," +
+ " {\"fieldName\": \"subject\", \"operator\": \"containsIgnoreCase\", \"value\": \"Apache James\"}" +
+ " ]" +
+ " }," +
+ " {\"fieldName\": \"subject\", \"operator\": \"containsIgnoreCase\", \"value\": \"Apache James\"}" +
+ " ]" +
+ "}";
+
+ given()
+ .body(query)
+ .when()
+ .post(USER.asString())
+ .then()
+ .statusCode(HttpStatus.BAD_REQUEST_400)
+ .body("statusCode", is(400))
+ .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType()))
+ .body("message", is(notNullValue()))
+ .body("details", is(notNullValue()));
}
}
@@ -209,11 +1342,14 @@ class DeletedMessagesVaultRoutesTest {
.when(vault)
.search(any(), any());
- String taskId = with()
- .queryParam("action", "restore")
- .post(USER.asString())
- .jsonPath()
- .get("taskId");
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(MATCH_ALL_QUERY)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
given()
.queryParam("action", "restore")
@@ -245,11 +1381,14 @@ class DeletedMessagesVaultRoutesTest {
.when(mockMessageManager)
.appendMessage(any(), any());
- String taskId = with()
- .queryParam("action", "restore")
- .post(USER.asString())
- .jsonPath()
- .get("taskId");
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(MATCH_ALL_QUERY)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
given()
.basePath(TasksRoutes.BASE)
@@ -275,11 +1414,14 @@ class DeletedMessagesVaultRoutesTest {
.when(mailboxManager)
.createMailbox(any(MailboxPath.class), any(MailboxSession.class));
- String taskId = with()
- .queryParam("action", "restore")
- .post(USER.asString())
- .jsonPath()
- .get("taskId");
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(MATCH_ALL_QUERY)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
given()
.basePath(TasksRoutes.BASE)
@@ -301,6 +1443,7 @@ class DeletedMessagesVaultRoutesTest {
void restoreShouldReturnATaskCreated() {
given()
.queryParam("action", "restore")
+ .body(MATCH_ALL_QUERY)
.when()
.post(USER.asString())
.then()
@@ -313,11 +1456,14 @@ class DeletedMessagesVaultRoutesTest {
vault.append(USER, DELETED_MESSAGE, new ByteArrayInputStream(CONTENT)).block();
vault.append(USER, DELETED_MESSAGE_2, new ByteArrayInputStream(CONTENT)).block();
- String taskId = with()
- .queryParam("action", "restore")
- .post(USER.asString())
- .jsonPath()
- .get("taskId");
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(MATCH_ALL_QUERY)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
given()
.basePath(TasksRoutes.BASE)
@@ -340,11 +1486,14 @@ class DeletedMessagesVaultRoutesTest {
vault.append(USER, DELETED_MESSAGE, new ByteArrayInputStream(CONTENT)).block();
vault.append(USER, DELETED_MESSAGE_2, new ByteArrayInputStream(CONTENT)).block();
- String taskId = with()
- .queryParam("action", "restore")
- .post(USER.asString())
- .jsonPath()
- .get("taskId");
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(MATCH_ALL_QUERY)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
given()
.basePath(TasksRoutes.BASE)
@@ -370,11 +1519,14 @@ class DeletedMessagesVaultRoutesTest {
vault.append(USER, DELETED_MESSAGE, new ByteArrayInputStream(CONTENT)).block();
vault.append(USER, DELETED_MESSAGE_2, new ByteArrayInputStream(CONTENT)).block();
- String taskId = with()
- .queryParam("action", "restore")
- .post(USER.asString())
- .jsonPath()
- .get("taskId");
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(MATCH_ALL_QUERY)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
given()
.basePath(TasksRoutes.BASE)
@@ -392,11 +1544,14 @@ class DeletedMessagesVaultRoutesTest {
vault.append(USER, DELETED_MESSAGE, new ByteArrayInputStream(CONTENT)).block();
vault.append(USER, DELETED_MESSAGE_2, new ByteArrayInputStream(CONTENT)).block();
- String taskId = with()
- .queryParam("action", "restore")
- .post(USER.asString())
- .jsonPath()
- .get("taskId");
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(MATCH_ALL_QUERY)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
given()
.basePath(TasksRoutes.BASE)
@@ -416,11 +1571,14 @@ class DeletedMessagesVaultRoutesTest {
vault.append(USER, DELETED_MESSAGE, new ByteArrayInputStream(CONTENT)).block();
vault.append(USER, DELETED_MESSAGE_2, new ByteArrayInputStream(CONTENT)).block();
- String taskId = with()
- .queryParam("action", "restore")
- .post(USER.asString())
- .jsonPath()
- .get("taskId");
+ String taskId =
+ given()
+ .queryParam("action", "restore")
+ .body(MATCH_ALL_QUERY)
+ .when()
+ .post(USER.asString())
+ .jsonPath()
+ .get("taskId");
given()
.basePath(TasksRoutes.BASE)
@@ -449,9 +1607,31 @@ class DeletedMessagesVaultRoutesTest {
}
}
+ private Stream<InputStream> restoreMessageContents(User user) throws Exception {
+ return restoreMailboxMessages(user).stream()
+ .map(this::fullContent);
+ }
+
private List<MessageResult> restoreMailboxMessages(User user) throws Exception {
MailboxSession session = mailboxManager.createSystemSession(user.asString());
MessageManager messageManager = mailboxManager.getMailbox(MailboxPath.forUser(user.asString(), RESTORE_MAILBOX_NAME), session);
return ImmutableList.copyOf(messageManager.getMessages(MessageRange.all(), FetchGroupImpl.MINIMAL, session));
}
+
+ private DeletedMessage.Builder.RequireHasAttachment<DeletedMessage.Builder.FinalStage> messageWithAttachmentBuilder() {
+ return DeletedMessage.builder()
+ .messageId(InMemoryMessageId.of(MESSAGE_ID_GENERATOR.incrementAndGet()))
+ .originMailboxes(MAILBOX_ID_1)
+ .user(USER)
+ .deliveryDate(DELIVERY_DATE)
+ .deletionDate(DELETION_DATE)
+ .sender(MaybeSender.of(SENDER))
+ .recipients(RECIPIENT1, RECIPIENT2);
+ }
+
+ private DeletedMessage storeDeletedMessage(DeletedMessage deletedMessage) {
+ Mono.from(vault.append(USER, deletedMessage, new ByteArrayInputStream(CONTENT)))
+ .block();
+ return deletedMessage;
+ }
}
\ No newline at end of file
diff --git a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/query/QueryTranslatorTest.java b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/query/QueryTranslatorTest.java
index 6689781..f159503 100644
--- a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/query/QueryTranslatorTest.java
+++ b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/query/QueryTranslatorTest.java
@@ -47,6 +47,13 @@ class QueryTranslatorTest {
}
@Test
+ void translateShouldNotThrowWhenPassingNullOperator() {
+ String nullOperator = null;
+ assertThatCode(() -> queryTranslator.translate(new QueryDTO(nullOperator, ImmutableList.of())))
+ .doesNotThrowAnyException();
+ }
+
+ @Test
void translateShouldThrowWhenPassingNestedQuery() {
assertThatThrownBy(() -> queryTranslator.translate(QueryDTO.and(
QueryDTO.and(new CriterionDTO(FieldName.SUBJECT.getValue(), Operator.CONTAINS.getValue(), "james"))
diff --git a/src/site/markdown/server/manage-webadmin.md b/src/site/markdown/server/manage-webadmin.md
index a660766..f039c15 100644
--- a/src/site/markdown/server/manage-webadmin.md
+++ b/src/site/markdown/server/manage-webadmin.md
@@ -2561,14 +2561,104 @@ Deleted messages of a specific user can be restored by calling the following end
```
curl -XPOST http://ip:port/deletedMessages/user/userToRestore@domain.ext?action=restore
+
+{"
+ "combinator": "and",
+ "criteria": [
+ {
+ "fieldName": "subject",
+ "operator": "containsIgnoreCase",
+ "value": "Apache James"
+ },
+ {
+ "fieldName": "deliveryDate",
+ "operator": "beforeOrEquals",
+ "value": "2014-10-30T14:12:00Z"
+ },
+ {
+ "fieldName": "deletionDate",
+ "operator": "afterOrEquals",
+ "value": "2015-10-20T09:08:00Z"
+ },
+ {
+ "fieldName": "recipients","
+ "operator": "contains","
+ "value": "recipient@james.org"
+ },
+ {
+ "fieldName": "hasAttachment",
+ "operator": "equals",
+ "value": "false"
+ },
+ {
+ "fieldName": "sender",
+ "operator": "equals",
+ "value": "sender@apache.org"
+ },
+ {
+ "fieldName": "originMailboxes",
+ "operator": "contains",
+ "value": "02874f7c-d10e-102f-acda-0015176f7922"
+ }
+ ]
+};
```
-**All** messages in the Deleted Messages Vault of an specified user will be appended to his 'Restored-Messages' mailbox, which will be created if needed.
+The requested Json body is made from list of criterion objects which have following structure:
+```
+{
+ "fieldName": "supportedFieldName",
+ "operator": "supportedOperator",
+ "testedValue": "plain string represents for the matching value of corresponding field"
+}
+```
+Deleted Messages which are matched with **all** criterions in the query body will be restored. Here are list of supported fieldName for the restoring:
+ - subject: represents for deleted message `subject` field matching. Supports below string operators:
+ - contains
+ - containsIgnoreCase
+ - equals
+ - equalsIgnoreCase
+ - deliveryDate: represents for deleted message `deliveryDate` field matching. Tested value should follow the right date time with zone offset format (ISO-8601) like
+ `2008-09-15T15:53:00+05:00` or `2008-09-15T15:53:00Z`
+ Supports below date time operators:
+ - beforeOrEquals: is the deleted message's `deliveryDate` before or equals the time of tested value.
+ - afterOrEquals: is the deleted message's `deliveryDate` after or equals the time of tested value
+ - deletionDate: represents for deleted message `deletionDate` field matching. Tested value & Supports operators: similar to `deliveryDate`
+ - sender: represents for deleted message `sender` field matching. Tested value should be a valid mail address. Supports mail address operator:
+ - equals: does the tested sender equal to the sender of the tested deleted message ?
+ - recipients: represents for deleted message `recipients` field matching. Tested value should be a valid mail address. Supports list mail address operator:
+ - contains: does the tested deleted message's recipients contain tested recipient ?
+ - hasAttachment: represents for deleted message `hasAttachment` field matching. Tested value could be `false` or `true`. Supports boolean operator:
+ - equals: does the tested deleted message's hasAttachment property equal to the tested hasAttachment value?
+ - originMailboxes: represents for deleted message `originMailboxes` field matching. Tested value is a string serialized of mailbox id. Supports list mailbox id operators:
+ - contains: does the tested deleted message's originMailbox ids contain tested mailbox id ?
+
+Messages in the Deleted Messages Vault of an specified user that are matched with Query Json Object in the body will be appended to his 'Restored-Messages' mailbox, which will be created if needed.
-**Note**:
- - Restoring matched messages by queries is not supported yet
+**Note**:
- Query parameter `action` is required and should have value `restore` to represent for restoring feature. Otherwise, a bad request response will be returned
- Query parameter `action` is case sensitive
+ - fieldName & operator for passing to the routes are case sensitive
+ - Currently, we only support query combinator `and` value, otherwise, requests will be rejected
+ - If you only want to restore by only one criterion, the json body could be simplified to a single criterion:
+```
+{
+ "fieldName": "subject",
+ "operator": "containsIgnoreCase",
+ "value": "Apache James"
+}
+```
+ - For restoring all deleted messages, passing a query json with empty criterion list to represent `matching all deleted messages`:
+```
+{
+ "combinator": "and",
+ "criteria": []
+}
+```
+
+**Warning**: Current web-admin uses `US` locale as the default. Therefore, there might be some conflicts when using String `containsIgnoreCase` comparators to apply
+on the String data of other special locales stored in the Vault. More details at [JIRA](https://issues.apache.org/jira/browse/MAILBOX-384)
+
Response code:
- 201: Task for restoring deleted has been created
@@ -2576,13 +2666,16 @@ Response code:
- action query param is not present
- action query param is not a valid action
- user parameter is invalid
+ - can not parse the JSON body
+ - Json query object contains unsupported operator, fieldName
+ - Json query object values violate parsing rules
The scheduled task will have the following type `deletedMessages/restore` and the following `additionalInformation`:
```
{
"successfulRestoreCount": 47,
- "errorRestoreCount": 0
+ "errorRestoreCount": 0,
"user": "userToRestore@domain.ext"
}
```
---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org