JAMES-2555 Implementing re-indexing routes This allows re-indexing globally, per-user, per-mailbox and per-message
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/a68dae1c Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/a68dae1c Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/a68dae1c Branch: refs/heads/master Commit: a68dae1c14fbe0b8740e0ac183b392f6936d741c Parents: 05b38b4 Author: Benoit Tellier <btell...@linagora.com> Authored: Fri Oct 12 11:31:03 2018 +0700 Committer: Benoit Tellier <btell...@linagora.com> Committed: Tue Oct 23 08:43:21 2018 +0700 ---------------------------------------------------------------------- .../tools/indexer/ReIndexerPerformer.java | 8 +- .../protocols/webadmin/webadmin-mailbox/pom.xml | 5 + .../james/webadmin/routes/ReindexingRoutes.java | 179 +++++ .../webadmin/routes/ReindexingRoutesTest.java | 719 +++++++++++++++++++ 4 files changed, 907 insertions(+), 4 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/a68dae1c/mailbox/tools/indexer/src/main/java/org/apache/mailbox/tools/indexer/ReIndexerPerformer.java ---------------------------------------------------------------------- diff --git a/mailbox/tools/indexer/src/main/java/org/apache/mailbox/tools/indexer/ReIndexerPerformer.java b/mailbox/tools/indexer/src/main/java/org/apache/mailbox/tools/indexer/ReIndexerPerformer.java index 625adda..92de5be 100644 --- a/mailbox/tools/indexer/src/main/java/org/apache/mailbox/tools/indexer/ReIndexerPerformer.java +++ b/mailbox/tools/indexer/src/main/java/org/apache/mailbox/tools/indexer/ReIndexerPerformer.java @@ -49,7 +49,7 @@ import org.slf4j.LoggerFactory; import com.github.fge.lambdas.Throwing; import com.google.common.collect.ImmutableList; -class ReIndexerPerformer { +public class ReIndexerPerformer { private static final Logger LOGGER = LoggerFactory.getLogger(ReIndexerPerformer.class); private static final int NO_LIMIT = 0; @@ -60,9 +60,9 @@ class ReIndexerPerformer { private final MailboxSessionMapperFactory mailboxSessionMapperFactory; @Inject - ReIndexerPerformer(MailboxManager mailboxManager, - ListeningMessageSearchIndex messageSearchIndex, - MailboxSessionMapperFactory mailboxSessionMapperFactory) { + public ReIndexerPerformer(MailboxManager mailboxManager, + ListeningMessageSearchIndex messageSearchIndex, + MailboxSessionMapperFactory mailboxSessionMapperFactory) { this.mailboxManager = mailboxManager; this.messageSearchIndex = messageSearchIndex; this.mailboxSessionMapperFactory = mailboxSessionMapperFactory; http://git-wip-us.apache.org/repos/asf/james-project/blob/a68dae1c/server/protocols/webadmin/webadmin-mailbox/pom.xml ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-mailbox/pom.xml b/server/protocols/webadmin/webadmin-mailbox/pom.xml index 8499f92..60782d7 100644 --- a/server/protocols/webadmin/webadmin-mailbox/pom.xml +++ b/server/protocols/webadmin/webadmin-mailbox/pom.xml @@ -61,6 +61,11 @@ </dependency> <dependency> <groupId>${james.groupId}</groupId> + <artifactId>apache-james-mailbox-tools-indexer</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>${james.groupId}</groupId> <artifactId>apache-james-mailbox-quota-search</artifactId> </dependency> <dependency> http://git-wip-us.apache.org/repos/asf/james-project/blob/a68dae1c/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/routes/ReindexingRoutes.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/routes/ReindexingRoutes.java b/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/routes/ReindexingRoutes.java new file mode 100644 index 0000000..512ef57 --- /dev/null +++ b/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/routes/ReindexingRoutes.java @@ -0,0 +1,179 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.webadmin.routes; + +import javax.inject.Inject; + +import org.apache.james.core.User; +import org.apache.james.mailbox.MailboxManager; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.MessageUid; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxNotFoundException; +import org.apache.james.mailbox.indexer.ReIndexer; +import org.apache.james.mailbox.model.MailboxId; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.task.Task; +import org.apache.james.task.TaskId; +import org.apache.james.task.TaskManager; +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.JsonTransformer; +import org.eclipse.jetty.http.HttpStatus; + +import com.github.fge.lambdas.supplier.ThrowingSupplier; + +import spark.Request; +import spark.Response; +import spark.Service; + +public class ReindexingRoutes implements Routes { + private static final String BASE_PATH = "/mailboxIndex"; + private static final String USER_PARAM = ":user"; + private static final String MAILBOX_PARAM = ":mailbox"; + private static final String UID_PARAM = ":uid"; + private static final String USER_PATH = BASE_PATH + "/users/" + USER_PARAM; + private static final String MAILBOX_PATH = USER_PATH + "/mailboxes/" + MAILBOX_PARAM; + private static final String MESSAGE_PATH = MAILBOX_PATH + "/mails/" + UID_PARAM; + + private final TaskManager taskManager; + private final MailboxManager mailboxManager; + private final MailboxId.Factory mailboxIdFactory; + private final ReIndexer reIndexer; + private final JsonTransformer jsonTransformer; + + @Inject + public ReindexingRoutes(TaskManager taskManager, MailboxManager mailboxManager, MailboxId.Factory mailboxIdFactory, ReIndexer reIndexer, JsonTransformer jsonTransformer) { + this.taskManager = taskManager; + this.mailboxManager = mailboxManager; + this.mailboxIdFactory = mailboxIdFactory; + this.reIndexer = reIndexer; + this.jsonTransformer = jsonTransformer; + } + + @Override + public String getBasePath() { + return BASE_PATH; + } + + @Override + public void define(Service service) { + service.post(BASE_PATH, this::reIndexAll, jsonTransformer); + service.post(USER_PATH, this::reIndexUser, jsonTransformer); + service.post(MAILBOX_PATH, this::reIndexMailbox, jsonTransformer); + service.post(MESSAGE_PATH, this::reIndexMessage, jsonTransformer); + } + + private TaskIdDto reIndexAll(Request request, Response response) { + return wrap(request, response, + () -> reIndexer.reIndex()); + } + + private TaskIdDto reIndexUser(Request request, Response response) { + return wrap(request, response, + () -> reIndexer.reIndex(extractUser(request))); + } + + private TaskIdDto reIndexMailbox(Request request, Response response) { + return wrap(request, response, + () -> reIndexer.reIndex(retrievePath(request))); + } + + private TaskIdDto reIndexMessage(Request request, Response response) { + return wrap(request, response, + () -> reIndexer.reIndex(retrievePath(request), extractUid(request))); + } + + private TaskIdDto wrap(Request request, Response response, ThrowingSupplier<Task> taskGenerator) { + enforceTaskParameter(request); + Task task = taskGenerator.get(); + TaskId taskId = taskManager.submit(task); + return TaskIdDto.respond(response, taskId); + } + + private User extractUser(Request request) { + try { + return User.fromUsername(request.params(USER_PARAM)); + } catch (Exception e) { + throw ErrorResponder.builder() + .statusCode(HttpStatus.BAD_REQUEST_400) + .type(ErrorResponder.ErrorType.INVALID_ARGUMENT) + .message("Error while parsing 'user'") + .cause(e) + .haltError(); + } + } + + private MailboxPath retrievePath(Request request) throws MailboxException { + return toMailboxPath(extractUser(request), extractMailboxId(request)); + } + + private MailboxId extractMailboxId(Request request) { + try { + return mailboxIdFactory.fromString(request.params(MAILBOX_PARAM)); + } catch (Exception e) { + throw ErrorResponder.builder() + .statusCode(HttpStatus.BAD_REQUEST_400) + .type(ErrorResponder.ErrorType.INVALID_ARGUMENT) + .message("Error while parsing 'mailbox'") + .cause(e) + .haltError(); + } + } + + private MessageUid extractUid(Request request) { + try { + return MessageUid.of(Long.valueOf(request.params(UID_PARAM))); + } catch (NumberFormatException e) { + throw ErrorResponder.builder() + .statusCode(HttpStatus.BAD_REQUEST_400) + .type(ErrorResponder.ErrorType.INVALID_ARGUMENT) + .message("'uid' needs to be a parsable long") + .cause(e) + .haltError(); + } + } + + private MailboxPath toMailboxPath(User user, MailboxId mailboxId) throws MailboxException { + try { + MailboxSession systemSession = mailboxManager.createSystemSession(user.asString()); + return mailboxManager.getMailbox(mailboxId, systemSession).getMailboxPath(); + } catch (MailboxNotFoundException e) { + throw ErrorResponder.builder() + .statusCode(HttpStatus.BAD_REQUEST_400) + .type(ErrorResponder.ErrorType.INVALID_ARGUMENT) + .message("mailbox not found") + .cause(e) + .haltError(); + } + } + + private void enforceTaskParameter(Request request) { + String task = request.queryParams("task"); + if (!"reIndex".equals(task)) { + throw ErrorResponder.builder() + .statusCode(HttpStatus.BAD_REQUEST_400) + .type(ErrorResponder.ErrorType.INVALID_ARGUMENT) + .message("task query parameter is mandatory. The only supported value is `reIndex`") + .haltError(); + } + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/a68dae1c/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/ReindexingRoutesTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/ReindexingRoutesTest.java b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/ReindexingRoutesTest.java new file mode 100644 index 0000000..dd20ca6 --- /dev/null +++ b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/ReindexingRoutesTest.java @@ -0,0 +1,719 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.webadmin.routes; + +import static io.restassured.RestAssured.given; +import static io.restassured.RestAssured.when; +import static io.restassured.RestAssured.with; +import static org.apache.james.webadmin.WebAdminServer.NO_CONFIGURATION; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.MessageManager; +import org.apache.james.mailbox.acl.SimpleGroupMembershipResolver; +import org.apache.james.mailbox.indexer.ReIndexer; +import org.apache.james.mailbox.inmemory.InMemoryId; +import org.apache.james.mailbox.inmemory.InMemoryMailboxManager; +import org.apache.james.mailbox.inmemory.manager.InMemoryIntegrationResources; +import org.apache.james.mailbox.model.ComposedMessageId; +import org.apache.james.mailbox.model.MailboxId; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.MailboxMessage; +import org.apache.james.mailbox.store.search.ListeningMessageSearchIndex; +import org.apache.james.metrics.logger.DefaultMetricFactory; +import org.apache.james.task.MemoryTaskManager; +import org.apache.james.webadmin.WebAdminServer; +import org.apache.james.webadmin.WebAdminUtils; +import org.apache.james.webadmin.utils.ErrorResponder; +import org.apache.james.webadmin.utils.JsonTransformer; +import org.apache.mailbox.tools.indexer.FullReindexingTask; +import org.apache.mailbox.tools.indexer.ReIndexerImpl; +import org.apache.mailbox.tools.indexer.ReIndexerPerformer; +import org.apache.mailbox.tools.indexer.SingleMailboxReindexingTask; +import org.apache.mailbox.tools.indexer.SingleMessageReindexingTask; +import org.apache.mailbox.tools.indexer.UserReindexingTask; +import org.eclipse.jetty.http.HttpStatus; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import io.restassured.RestAssured; + +class ReindexingRoutesTest { + private static final String USERNAME = "be...@apache.org"; + private static final MailboxPath INBOX = MailboxPath.forUser(USERNAME, "INBOX"); + + private WebAdminServer webAdminServer; + private ListeningMessageSearchIndex searchIndex; + private InMemoryMailboxManager mailboxManager; + + @BeforeEach + void beforeEach() throws Exception { + mailboxManager = new InMemoryIntegrationResources().createMailboxManager(new SimpleGroupMembershipResolver()); + MemoryTaskManager taskManager = new MemoryTaskManager(); + InMemoryId.Factory mailboxIdFactory = new InMemoryId.Factory(); + searchIndex = mock(ListeningMessageSearchIndex.class); + ReIndexer reIndexer = new ReIndexerImpl( + new ReIndexerPerformer( + mailboxManager, + searchIndex, + mailboxManager.getMapperFactory())); + JsonTransformer jsonTransformer = new JsonTransformer(); + + webAdminServer = WebAdminUtils.createWebAdminServer( + new DefaultMetricFactory(), + new TasksRoutes(taskManager, jsonTransformer), + new ReindexingRoutes( + taskManager, + mailboxManager, + mailboxIdFactory, + reIndexer, + jsonTransformer)); + webAdminServer.configure(NO_CONFIGURATION); + webAdminServer.await(); + + RestAssured.requestSpecification = WebAdminUtils.buildRequestSpecification(webAdminServer).build(); + RestAssured.enableLoggingOfRequestAndResponseIfValidationFails(); + } + + @AfterEach + void tearDown() { + webAdminServer.destroy(); + } + + @Nested + class FullReprocessing { + @Nested + class Validation { + @Test + void fullReprocessingShouldFailWithNoTask() { + when() + .post("/mailboxIndex") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .body("statusCode", is(400)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is("task query parameter is mandatory. The only supported value is `reIndex`")); + } + + @Test + void fullReprocessingShouldFailWithBadTask() { + when() + .post("/mailboxIndex?task=bad") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .body("statusCode", is(400)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is("task query parameter is mandatory. The only supported value is `reIndex`")); + } + } + + @Nested + class TaskDetails { + @Test + void fullReprocessingShouldNotFailWhenNoMail() { + String taskId = with() + .post("/mailboxIndex?task=reIndex") + .jsonPath() + .get("taskId"); + + given() + .basePath(TasksRoutes.BASE) + .when() + .get(taskId + "/await") + .then() + .body("status", is("completed")) + .body("taskId", is(notNullValue())) + .body("type", is(FullReindexingTask.FULL_RE_INDEXING)) + .body("additionalInformation.successfullyReprocessMailCount", is(0)) + .body("additionalInformation.failedReprocessedMailCount", is(0)) + .body("startedDate", is(notNullValue())) + .body("submitDate", is(notNullValue())) + .body("completedDate", is(notNullValue())); + } + + @Test + void fullReprocessingShouldReturnTaskDetailsWhenMail() throws Exception { + MailboxSession systemSession = mailboxManager.createSystemSession(USERNAME); + mailboxManager.createMailbox(INBOX, systemSession).get(); + mailboxManager.getMailbox(INBOX, systemSession) + .appendMessage( + MessageManager.AppendCommand.builder().build("header: value\r\n\r\nbody"), + systemSession); + + String taskId = with() + .post("/mailboxIndex?task=reIndex") + .jsonPath() + .get("taskId"); + + given() + .basePath(TasksRoutes.BASE) + .when() + .get(taskId + "/await") + .then() + .body("status", is("completed")) + .body("taskId", is(notNullValue())) + .body("type", is(FullReindexingTask.FULL_RE_INDEXING)) + .body("additionalInformation.successfullyReprocessMailCount", is(1)) + .body("additionalInformation.failedReprocessedMailCount", is(0)) + .body("startedDate", is(notNullValue())) + .body("submitDate", is(notNullValue())) + .body("completedDate", is(notNullValue())); + } + } + + @Nested + class SideEffects { + @Test + void fullReprocessingShouldPerformReprocessingWhenMail() throws Exception { + MailboxSession systemSession = mailboxManager.createSystemSession(USERNAME); + MailboxId mailboxId = mailboxManager.createMailbox(INBOX, systemSession).get(); + ComposedMessageId createdMessage = mailboxManager.getMailbox(INBOX, systemSession) + .appendMessage( + MessageManager.AppendCommand.builder().build("header: value\r\n\r\nbody"), + systemSession); + + String taskId = with() + .post("/mailboxIndex?task=reIndex") + .jsonPath() + .get("taskId"); + + with() + .basePath(TasksRoutes.BASE) + .get(taskId + "/await") + .then() + .body("status", is("completed")); + + + ArgumentCaptor<MailboxMessage> messageCaptor = ArgumentCaptor.forClass(MailboxMessage.class); + ArgumentCaptor<Mailbox> mailboxCaptor1 = ArgumentCaptor.forClass(Mailbox.class); + ArgumentCaptor<Mailbox> mailboxCaptor2 = ArgumentCaptor.forClass(Mailbox.class); + + verify(searchIndex).deleteAll(any(MailboxSession.class), mailboxCaptor1.capture()); + verify(searchIndex).add(any(MailboxSession.class), mailboxCaptor2.capture(), messageCaptor.capture()); + verifyNoMoreInteractions(searchIndex); + + assertThat(mailboxCaptor1.getValue()).matches(mailbox -> mailbox.getMailboxId().equals(mailboxId)); + assertThat(mailboxCaptor2.getValue()).matches(mailbox -> mailbox.getMailboxId().equals(mailboxId)); + assertThat(messageCaptor.getValue()).matches(message -> message.getMailboxId().equals(mailboxId) + && message.getUid().equals(createdMessage.getUid())); + } + } + } + + @Nested + class UserReprocessing { + @Nested + class Validation { + @Test + void userReprocessingShouldFailWithNoTask() { + when() + .post("/mailboxIndex/users/" + USERNAME) + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .body("statusCode", is(400)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is("task query parameter is mandatory. The only supported value is `reIndex`")); + } + + @Test + void userReprocessingShouldFailWithBadTask() { + when() + .post("/mailboxIndex/users/" + USERNAME + "?task=bad") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .body("statusCode", is(400)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is("task query parameter is mandatory. The only supported value is `reIndex`")); + } + + @Test + void userReprocessingShouldFailWithBadUser() { + when() + .post("/mailboxIndex/users/bad@bad@bad?task=reIndex") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .body("statusCode", is(400)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is("Error while parsing 'user'")); + } + } + + @Nested + class TaskDetails { + @Test + void userReprocessingShouldNotFailWhenNoMail() { + String taskId = with() + .post("/mailboxIndex/users/" + USERNAME + "?task=reIndex") + .jsonPath() + .get("taskId"); + + given() + .basePath(TasksRoutes.BASE) + .when() + .get(taskId + "/await") + .then() + .body("status", is("completed")) + .body("taskId", is(notNullValue())) + .body("type", is(UserReindexingTask.USER_RE_INDEXING)) + .body("additionalInformation.user", is("be...@apache.org")) + .body("additionalInformation.successfullyReprocessMailCount", is(0)) + .body("additionalInformation.failedReprocessedMailCount", is(0)) + .body("startedDate", is(notNullValue())) + .body("submitDate", is(notNullValue())) + .body("completedDate", is(notNullValue())); + } + + @Test + void userReprocessingShouldReturnTaskDetailsWhenMail() throws Exception { + MailboxSession systemSession = mailboxManager.createSystemSession(USERNAME); + mailboxManager.createMailbox(INBOX, systemSession).get(); + mailboxManager.getMailbox(INBOX, systemSession) + .appendMessage( + MessageManager.AppendCommand.builder().build("header: value\r\n\r\nbody"), + systemSession); + + String taskId = with() + .post("/mailboxIndex/users/" + USERNAME + "?task=reIndex") + .jsonPath() + .get("taskId"); + + given() + .basePath(TasksRoutes.BASE) + .when() + .get(taskId + "/await") + .then() + .body("status", is("completed")) + .body("taskId", is(notNullValue())) + .body("type", is(UserReindexingTask.USER_RE_INDEXING)) + .body("additionalInformation.user", is("be...@apache.org")) + .body("additionalInformation.successfullyReprocessMailCount", is(1)) + .body("additionalInformation.failedReprocessedMailCount", is(0)) + .body("startedDate", is(notNullValue())) + .body("submitDate", is(notNullValue())) + .body("completedDate", is(notNullValue())); + } + } + + @Nested + class SideEffects { + @Test + void userReprocessingShouldPerformReprocessingWhenMail() throws Exception { + MailboxSession systemSession = mailboxManager.createSystemSession(USERNAME); + MailboxId mailboxId = mailboxManager.createMailbox(INBOX, systemSession).get(); + ComposedMessageId createdMessage = mailboxManager.getMailbox(INBOX, systemSession) + .appendMessage( + MessageManager.AppendCommand.builder().build("header: value\r\n\r\nbody"), + systemSession); + + String taskId = with() + .post("/mailboxIndex/users/" + USERNAME + "?task=reIndex") + .jsonPath() + .get("taskId"); + + given() + .basePath(TasksRoutes.BASE) + .when() + .get(taskId + "/await") + .then() + .body("status", is("completed")); + + + ArgumentCaptor<MailboxMessage> messageCaptor = ArgumentCaptor.forClass(MailboxMessage.class); + ArgumentCaptor<Mailbox> mailboxCaptor1 = ArgumentCaptor.forClass(Mailbox.class); + ArgumentCaptor<Mailbox> mailboxCaptor2 = ArgumentCaptor.forClass(Mailbox.class); + + verify(searchIndex).deleteAll(any(MailboxSession.class), mailboxCaptor1.capture()); + verify(searchIndex).add(any(MailboxSession.class), mailboxCaptor2.capture(), messageCaptor.capture()); + verifyNoMoreInteractions(searchIndex); + + assertThat(mailboxCaptor1.getValue()).matches(mailbox -> mailbox.getMailboxId().equals(mailboxId)); + assertThat(mailboxCaptor2.getValue()).matches(mailbox -> mailbox.getMailboxId().equals(mailboxId)); + assertThat(messageCaptor.getValue()).matches(message -> message.getMailboxId().equals(mailboxId) + && message.getUid().equals(createdMessage.getUid())); + } + } + } + + @Nested + class MailboxReprocessing { + @Nested + class Validation { + @Test + void mailboxReprocessingShouldFailWithNoTask() throws Exception { + MailboxSession systemSession = mailboxManager.createSystemSession(USERNAME); + MailboxId mailboxId = mailboxManager.createMailbox(INBOX, systemSession).get(); + + when() + .post("/mailboxIndex/users/" + USERNAME + "/mailboxes/" + mailboxId.serialize()) + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .body("statusCode", is(400)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is("task query parameter is mandatory. The only supported value is `reIndex`")); + } + + @Test + void mailboxReprocessingShouldFailWithBadTask() throws Exception { + MailboxSession systemSession = mailboxManager.createSystemSession(USERNAME); + MailboxId mailboxId = mailboxManager.createMailbox(INBOX, systemSession).get(); + + when() + .post("/mailboxIndex/users/" + USERNAME + "/mailboxes/" + mailboxId.serialize() + "?task=bad") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .body("statusCode", is(400)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is("task query parameter is mandatory. The only supported value is `reIndex`")); + } + + @Test + void mailboxReprocessingShouldFailWithBadUser() throws Exception { + MailboxSession systemSession = mailboxManager.createSystemSession(USERNAME); + MailboxId mailboxId = mailboxManager.createMailbox(INBOX, systemSession).get(); + + when() + .post("/mailboxIndex/users/bad@bad@bad/mailboxes/" + mailboxId.serialize() + "?task=reIndex") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .body("statusCode", is(400)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is("Error while parsing 'user'")); + } + + @Test + void mailboxReprocessingShouldFailWithBadMailboxId() { + when() + .post("/mailboxIndex/users/" + USERNAME + "/mailboxes/bad?task=reIndex") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .body("statusCode", is(400)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is("Error while parsing 'mailbox'")); + } + + @Test + void mailboxReprocessingShouldFailWithNonExistentMailboxId() { + when() + .post("/mailboxIndex/users/" + USERNAME + "/mailboxes/36?task=reIndex") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .body("statusCode", is(400)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is("mailbox not found")); + } + + @Test + void mailboxReprocessingShouldFailWithNonExistentUser() { + when() + .post("/mailboxIndex/users/notfo...@domain.tld/mailboxes/36?task=reIndex") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .body("statusCode", is(400)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is("mailbox not found")); + } + } + + @Nested + class TaskDetails { + @Test + void mailboxReprocessingShouldNotFailWhenNoMail() throws Exception { + MailboxSession systemSession = mailboxManager.createSystemSession(USERNAME); + MailboxId mailboxId = mailboxManager.createMailbox(INBOX, systemSession).get(); + + String taskId = when() + .post("/mailboxIndex/users/" + USERNAME + "/mailboxes/" + mailboxId.serialize() + "?task=reIndex") + .jsonPath() + .get("taskId"); + + given() + .basePath(TasksRoutes.BASE) + .when() + .get(taskId + "/await") + .then() + .body("status", is("completed")) + .body("taskId", is(notNullValue())) + .body("type", is(SingleMailboxReindexingTask.MAILBOX_RE_INDEXING)) + .body("additionalInformation.mailboxPath", is("#private:be...@apache.org:INBOX")) + .body("additionalInformation.successfullyReprocessMailCount", is(0)) + .body("additionalInformation.failedReprocessedMailCount", is(0)) + .body("startedDate", is(notNullValue())) + .body("submitDate", is(notNullValue())) + .body("completedDate", is(notNullValue())); + } + + @Test + void mailboxReprocessingShouldReturnTaskDetailsWhenMail() throws Exception { + MailboxSession systemSession = mailboxManager.createSystemSession(USERNAME); + MailboxId mailboxId = mailboxManager.createMailbox(INBOX, systemSession).get(); + mailboxManager.getMailbox(INBOX, systemSession) + .appendMessage( + MessageManager.AppendCommand.builder().build("header: value\r\n\r\nbody"), + systemSession); + + String taskId = when() + .post("/mailboxIndex/users/" + USERNAME + "/mailboxes/" + mailboxId.serialize() + "?task=reIndex") + .jsonPath() + .get("taskId"); + + given() + .basePath(TasksRoutes.BASE) + .when() + .get(taskId + "/await") + .then() + .body("status", is("completed")) + .body("taskId", is(notNullValue())) + .body("type", is(SingleMailboxReindexingTask.MAILBOX_RE_INDEXING)) + .body("additionalInformation.mailboxPath", is("#private:be...@apache.org:INBOX")) + .body("additionalInformation.successfullyReprocessMailCount", is(1)) + .body("additionalInformation.failedReprocessedMailCount", is(0)) + .body("startedDate", is(notNullValue())) + .body("submitDate", is(notNullValue())) + .body("completedDate", is(notNullValue())); + } + } + + @Nested + class SideEffects { + @Test + void mailboxReprocessingShouldPerformReprocessingWhenMail() throws Exception { + MailboxSession systemSession = mailboxManager.createSystemSession(USERNAME); + MailboxId mailboxId = mailboxManager.createMailbox(INBOX, systemSession).get(); + ComposedMessageId createdMessage = mailboxManager.getMailbox(INBOX, systemSession) + .appendMessage( + MessageManager.AppendCommand.builder().build("header: value\r\n\r\nbody"), + systemSession); + + String taskId = when() + .post("/mailboxIndex/users/" + USERNAME + "/mailboxes/" + mailboxId.serialize() + "?task=reIndex") + .jsonPath() + .get("taskId"); + + given() + .basePath(TasksRoutes.BASE) + .when() + .get(taskId + "/await") + .then() + .body("status", is("completed")); + + + ArgumentCaptor<MailboxMessage> messageCaptor = ArgumentCaptor.forClass(MailboxMessage.class); + ArgumentCaptor<Mailbox> mailboxCaptor1 = ArgumentCaptor.forClass(Mailbox.class); + ArgumentCaptor<Mailbox> mailboxCaptor2 = ArgumentCaptor.forClass(Mailbox.class); + + verify(searchIndex).deleteAll(any(MailboxSession.class), mailboxCaptor1.capture()); + verify(searchIndex).add(any(MailboxSession.class), mailboxCaptor2.capture(), messageCaptor.capture()); + verifyNoMoreInteractions(searchIndex); + + assertThat(mailboxCaptor1.getValue()).matches(mailbox -> mailbox.getMailboxId().equals(mailboxId)); + assertThat(mailboxCaptor2.getValue()).matches(mailbox -> mailbox.getMailboxId().equals(mailboxId)); + assertThat(messageCaptor.getValue()).matches(message -> message.getMailboxId().equals(mailboxId) + && message.getUid().equals(createdMessage.getUid())); + } + } + } + + @Nested + class MessageReprocessing { + @Nested + class Validation { + @Test + void messageReprocessingShouldFailWithNoTask() throws Exception { + MailboxSession systemSession = mailboxManager.createSystemSession(USERNAME); + MailboxId mailboxId = mailboxManager.createMailbox(INBOX, systemSession).get(); + + when() + .post("/mailboxIndex/users/" + USERNAME + "/mailboxes/" + mailboxId.serialize() + "/mails/7") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .body("statusCode", is(400)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is("task query parameter is mandatory. The only supported value is `reIndex`")); + } + + @Test + void messageReprocessingShouldFailWithBadTask() throws Exception { + MailboxSession systemSession = mailboxManager.createSystemSession(USERNAME); + MailboxId mailboxId = mailboxManager.createMailbox(INBOX, systemSession).get(); + + when() + .post("/mailboxIndex/users/" + USERNAME + "/mailboxes/" + mailboxId.serialize() + "/mails/7?task=bad") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .body("statusCode", is(400)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is("task query parameter is mandatory. The only supported value is `reIndex`")); + } + + @Test + void messageReprocessingShouldFailWithBadUser() throws Exception { + MailboxSession systemSession = mailboxManager.createSystemSession(USERNAME); + MailboxId mailboxId = mailboxManager.createMailbox(INBOX, systemSession).get(); + + when() + .post("/mailboxIndex/users/bad@bad@bad/mailboxes/" + mailboxId.serialize() + "/mails/7?task=reIndex") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .body("statusCode", is(400)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is("Error while parsing 'user'")); + } + + @Test + void messageReprocessingShouldFailWithBadMailboxId() { + when() + .post("/mailboxIndex/users/" + USERNAME + "/mailboxes/bad/mails/7?task=reIndex") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .body("statusCode", is(400)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is("Error while parsing 'mailbox'")); + } + + @Test + void messageReprocessingShouldFailWithNonExistentMailboxId() { + when() + .post("/mailboxIndex/users/" + USERNAME + "/mailboxes/36/mails/7?task=reIndex") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .body("statusCode", is(400)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is("mailbox not found")); + } + + @Test + void messageReprocessingShouldFailWithBadUid() { + when() + .post("/mailboxIndex/users/" + USERNAME + "/mailboxes/36/mails/bad?task=reIndex") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .body("statusCode", is(400)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is("mailbox not found")); + } + } + + @Nested + class TaskDetails { + @Test + void messageReprocessingShouldNotFailWhenUidNotFound() throws Exception { + MailboxSession systemSession = mailboxManager.createSystemSession(USERNAME); + MailboxId mailboxId = mailboxManager.createMailbox(INBOX, systemSession).get(); + + String taskId = when() + .post("/mailboxIndex/users/" + USERNAME + "/mailboxes/" + mailboxId.serialize() + "/mails/1?task=reIndex") + .jsonPath() + .get("taskId"); + + given() + .basePath(TasksRoutes.BASE) + .when() + .get(taskId + "/await") + .then() + .body("status", is("completed")) + .body("taskId", is(notNullValue())) + .body("type", is(SingleMessageReindexingTask.MESSAGE_RE_INDEXING)) + .body("additionalInformation.mailboxPath", is("#private:be...@apache.org:INBOX")) + .body("additionalInformation.uid", is(1)) + .body("startedDate", is(notNullValue())) + .body("submitDate", is(notNullValue())) + .body("completedDate", is(notNullValue())); + } + + @Test + void messageReprocessingShouldReturnTaskDetailsWhenMail() throws Exception { + MailboxSession systemSession = mailboxManager.createSystemSession(USERNAME); + MailboxId mailboxId = mailboxManager.createMailbox(INBOX, systemSession).get(); + ComposedMessageId composedMessageId = mailboxManager.getMailbox(INBOX, systemSession) + .appendMessage( + MessageManager.AppendCommand.builder().build("header: value\r\n\r\nbody"), + systemSession); + + String taskId = when() + .post("/mailboxIndex/users/" + USERNAME + "/mailboxes/" + mailboxId.serialize() + "/mails/" + + composedMessageId.getUid().asLong() + "?task=reIndex") + .jsonPath() + .get("taskId"); + + given() + .basePath(TasksRoutes.BASE) + .when() + .get(taskId + "/await") + .then() + .body("status", is("completed")) + .body("taskId", is(notNullValue())) + .body("type", is(SingleMessageReindexingTask.MESSAGE_RE_INDEXING)) + .body("additionalInformation.mailboxPath", is("#private:be...@apache.org:INBOX")) + .body("additionalInformation.uid", is((int) composedMessageId.getUid().asLong())) + .body("startedDate", is(notNullValue())) + .body("submitDate", is(notNullValue())) + .body("completedDate", is(notNullValue())); + } + } + + @Nested + class SideEffects { + @Test + void mailboxReprocessingShouldPerformReprocessingWhenMail() throws Exception { + MailboxSession systemSession = mailboxManager.createSystemSession(USERNAME); + MailboxId mailboxId = mailboxManager.createMailbox(INBOX, systemSession).get(); + ComposedMessageId createdMessage = mailboxManager.getMailbox(INBOX, systemSession) + .appendMessage( + MessageManager.AppendCommand.builder().build("header: value\r\n\r\nbody"), + systemSession); + + String taskId = when() + .post("/mailboxIndex/users/" + USERNAME + "/mailboxes/" + mailboxId.serialize() + "/mails/" + + createdMessage.getUid().asLong() + "?task=reIndex") + .jsonPath() + .get("taskId"); + + given() + .basePath(TasksRoutes.BASE) + .when() + .get(taskId + "/await") + .then() + .body("status", is("completed")); + + + ArgumentCaptor<MailboxMessage> messageCaptor = ArgumentCaptor.forClass(MailboxMessage.class); + ArgumentCaptor<Mailbox> mailboxCaptor = ArgumentCaptor.forClass(Mailbox.class); + + verify(searchIndex).add(any(MailboxSession.class), mailboxCaptor.capture(), messageCaptor.capture()); + verifyNoMoreInteractions(searchIndex); + + assertThat(mailboxCaptor.getValue()).matches(mailbox -> mailbox.getMailboxId().equals(mailboxId)); + assertThat(messageCaptor.getValue()).matches(message -> message.getMailboxId().equals(mailboxId) + && message.getUid().equals(createdMessage.getUid())); + } + } + } + +} \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org For additional commands, e-mail: server-dev-h...@james.apache.org