This is an automated email from the ASF dual-hosted git repository. rcordier pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 3705f31b892a1db843c869154b0b97718ff8cc90 Author: vttran <[email protected]> AuthorDate: Tue Apr 9 11:02:38 2024 +0700 JAMES-4025 Rewrite some tests of JMAP draft on top of JMAP RFC-8621 (module: webadmin-integration-test) --- .../rabbitmq/FixingGhostMailboxTest.java | 141 +++-- ...RabbitMQReindexingWithEventDeadLettersTest.java | 54 +- ...ewProjectionHealthCheckIntegrationContract.java | 138 +++-- .../integration/ForwardIntegrationTest.java | 348 +++-------- .../james/webadmin/integration/TestFixture.java | 34 ++ .../vault/DeletedMessageVaultIntegrationTest.java | 675 +++++++++++---------- ...LinshareBlobExportMechanismIntegrationTest.java | 119 ++-- .../apache/james/jmap/JmapRFCCommonRequests.java | 314 ++++++++++ 8 files changed, 1120 insertions(+), 703 deletions(-) diff --git a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/FixingGhostMailboxTest.java b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/FixingGhostMailboxTest.java index 6418beffea..1f46a15d74 100644 --- a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/FixingGhostMailboxTest.java +++ b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/FixingGhostMailboxTest.java @@ -27,12 +27,10 @@ import static io.restassured.RestAssured.with; import static io.restassured.config.EncoderConfig.encoderConfig; import static io.restassured.config.RestAssuredConfig.newConfig; import static org.apache.james.backends.rabbitmq.RabbitMQFixture.calmlyAwait; -import static org.apache.james.jmap.HttpJmapAuthentication.authenticateJamesUser; -import static org.apache.james.jmap.LocalHostURIBuilder.baseUri; +import static org.apache.james.jmap.JmapRFCCommonRequests.UserCredential; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasKey; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; @@ -55,7 +53,7 @@ import org.apache.james.backends.cassandra.init.ClusterFactory; import org.apache.james.backends.cassandra.init.configuration.CassandraConfiguration; import org.apache.james.backends.cassandra.init.configuration.ClusterConfiguration; import org.apache.james.core.Username; -import org.apache.james.jmap.AccessToken; +import org.apache.james.jmap.JmapRFCCommonRequests; import org.apache.james.jmap.draft.JmapGuiceProbe; import org.apache.james.junit.categories.BasicFeature; import org.apache.james.junit.categories.Unstable; @@ -84,6 +82,7 @@ import org.apache.james.utils.WebAdminGuiceProbe; import org.apache.james.webadmin.WebAdminUtils; import org.apache.james.webadmin.routes.CassandraMailboxMergingRoutes; import org.apache.james.webadmin.routes.TasksRoutes; +import org.hamcrest.Matchers; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @@ -98,10 +97,6 @@ import io.restassured.specification.RequestSpecification; @Tag(BasicFeature.TAG) class FixingGhostMailboxTest { - private static final String NAME = "[0][0]"; - private static final String ARGUMENTS = "[0][1]"; - private static final String FIRST_MAILBOX = ARGUMENTS + ".list[0]"; - private static final String DOMAIN = "domain.tld"; private static final String CEDRIC = "cedric@" + DOMAIN; private static final String BOB = "bob@" + DOMAIN; @@ -136,7 +131,7 @@ class FixingGhostMailboxTest { .build()))) .build(); - private AccessToken accessToken; + private UserCredential aliceCredential; private MailboxProbeImpl mailboxProbe; private ACLProbe aclProbe; private ComposedMessageId message1; @@ -157,6 +152,7 @@ class FixingGhostMailboxTest { .setAccept(ContentType.JSON) .setConfig(newConfig().encoderConfig(encoderConfig().defaultContentCharset(StandardCharsets.UTF_8))) .setPort(jmapPort.getValue()) + .addHeader("accept", "application/json; jmapVersion=rfc-8621") .build(); RestAssured.enableLoggingOfRequestAndResponseIfValidationFails(); webadminSpecification = WebAdminUtils.buildRequestSpecification(webAdminProbe.getWebAdminPort()) @@ -166,7 +162,7 @@ class FixingGhostMailboxTest { .addDomain(DOMAIN) .addUser(ALICE, ALICE_SECRET) .addUser(BOB, BOB_SECRET); - accessToken = authenticateJamesUser(baseUri(jmapPort), Username.of(ALICE), ALICE_SECRET); + aliceCredential = JmapRFCCommonRequests.getUserCredential(Username.of(ALICE), ALICE_SECRET); CassandraProbe probe = server.getProbe(CassandraProbe.class); ClusterConfiguration cassandraConfiguration = probe.getConfiguration(); @@ -193,8 +189,24 @@ class FixingGhostMailboxTest { // trigger provisioning given() - .header("Authorization", accessToken.asString()) - .body("[[\"getMailboxes\", {}, \"#0\"]]") + .auth().basic(aliceCredential.username().asString(), aliceCredential.password()) + .body(""" + { + "using": [ + "urn:ietf:params:jmap:core", + "urn:ietf:params:jmap:mail" + ], + "methodCalls": [ + [ + "Mailbox/get", + { + "accountId": "%s", + "ids": null + }, + "c1" + ] + ] + }""".formatted(aliceCredential.accountId())) .when() .post("/jmap") .then() @@ -227,16 +239,31 @@ class FixingGhostMailboxTest { .timeout(THIRTY_SECONDS) .untilAsserted(() -> given() - .header("Authorization", accessToken.asString()) - .body("[[\"getMessageList\", {\"filter\":{\"inMailboxes\":[\"" + newAliceInbox.serialize() + "\"]}}, \"#0\"]]") + .auth().basic(aliceCredential.username().asString(), aliceCredential.password()) + .body(""" + { + "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail", "urn:apache:james:params:jmap:mail:shares" ], + "methodCalls": [ + [ + "Email/query", + { + "accountId": "%s", + "filter": { + "inMailbox": "%s" + } + }, + "c1" + ] + ] + } + """.formatted(aliceCredential.accountId(), newAliceInbox.serialize())) .when() .post("/jmap") .then() .statusCode(200) - .body(NAME, equalTo("messageList")) - .body(ARGUMENTS + ".messageIds", hasSize(1)) - .body(ARGUMENTS + ".messageIds", not(contains(message1.getMessageId().serialize()))) - .body(ARGUMENTS + ".messageIds", contains(message2.getMessageId().serialize()))); + .body("methodResponses[0][1].ids", hasSize(1)) + .body("methodResponses[0][1].ids", not(contains(message1.getMessageId().serialize()))) + .body("methodResponses[0][1].ids", contains(message2.getMessageId().serialize()))); } @@ -254,18 +281,35 @@ class FixingGhostMailboxTest { fixGhostMailboxes(newAliceInbox); - given() - .header("Authorization", accessToken.asString()) - .body("[[\"getMessageList\", {\"filter\":{\"inMailboxes\":[\"" + newAliceInbox.serialize() + "\"]}}, \"#0\"]]") + calmlyAwait + .timeout(THIRTY_SECONDS) + .untilAsserted(() -> given() + .auth().basic(aliceCredential.username().asString(), aliceCredential.password()) + .body(""" + { + "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail", "urn:apache:james:params:jmap:mail:shares" ], + "methodCalls": [ + [ + "Email/query", + { + "accountId": "%s", + "filter": { + "inMailbox": "%s" + } + }, + "c1" + ] + ] + } + """.formatted(aliceCredential.accountId(), newAliceInbox.serialize())) .when() .post("/jmap") .then() .statusCode(200) - .body(NAME, equalTo("messageList")) - .body(ARGUMENTS + ".messageIds", hasSize(2)) - .body(ARGUMENTS + ".messageIds", containsInAnyOrder( + .body("methodResponses[0][1].ids", hasSize(2)) + .body("methodResponses[0][1].ids", containsInAnyOrder( message1.getMessageId().serialize(), - message2.getMessageId().serialize())); + message2.getMessageId().serialize()))); } @Test @@ -276,15 +320,28 @@ class FixingGhostMailboxTest { fixGhostMailboxes(newAliceInbox); given() - .header("Authorization", accessToken.asString()) - .body("[[\"getMailboxes\", {\"ids\": [\"" + newAliceInbox.serialize() + "\"]}, \"#0\"]]") + .auth().basic(aliceCredential.username().asString(), aliceCredential.password()) + .header("accept", "application/json; jmapVersion=rfc-8621") + .body(""" + { + "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail", "urn:apache:james:params:jmap:mail:shares" ], + "methodCalls": [ + [ + "Mailbox/get", + { + "accountId": "%s", + "ids": ["%s"] + }, + "c1" + ] + ] + }""".formatted(aliceCredential.accountId(), newAliceInbox.serialize())) .when() .post("/jmap") .then() .statusCode(200) - .body(NAME, equalTo("mailboxes")) - .body(FIRST_MAILBOX + ".sharedWith", hasKey(BOB)) - .body(FIRST_MAILBOX + ".sharedWith", hasKey(CEDRIC)); + .body("methodResponses[0][1].list[0].rights", hasKey(BOB)) + .body("methodResponses[0][1].list[0].rights", hasKey(CEDRIC)); } @Test @@ -295,14 +352,30 @@ class FixingGhostMailboxTest { fixGhostMailboxes(newAliceInbox); given() - .header("Authorization", accessToken.asString()) - .body("[[\"getMailboxes\", {\"ids\": [\"" + aliceGhostInboxId.serialize() + "\"]}, \"#0\"]]") + .auth().basic(aliceCredential.username().asString(), aliceCredential.password()) + .body(""" + { + "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail", "urn:apache:james:params:jmap:mail:shares"], + "methodCalls": [ + [ + "Email/query", + { + "accountId": "%s", + "filter": { + "inMailbox": "%s" + } + }, + "c1" + ] + ] + } + """.formatted(aliceCredential.accountId(), aliceGhostInboxId.serialize())) .when() .post("/jmap") .then() .statusCode(200) - .body(NAME, equalTo("mailboxes")) - .body(ARGUMENTS + ".list", hasSize(0)); + .body("methodResponses[0][1].type", Matchers.is("invalidArguments")) + .body("methodResponses[0][1].description", Matchers.is(aliceGhostInboxId.serialize() + " can not be found")); } @Test diff --git a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQReindexingWithEventDeadLettersTest.java b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQReindexingWithEventDeadLettersTest.java index 360c40128b..0f53563638 100644 --- a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQReindexingWithEventDeadLettersTest.java +++ b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQReindexingWithEventDeadLettersTest.java @@ -20,13 +20,14 @@ package org.apache.james.webadmin.integration.rabbitmq; import static io.restassured.RestAssured.with; -import static org.apache.james.jmap.HttpJmapAuthentication.authenticateJamesUser; import static org.apache.james.jmap.JMAPTestingConstants.ALICE; import static org.apache.james.jmap.JMAPTestingConstants.ALICE_PASSWORD; import static org.apache.james.jmap.JMAPTestingConstants.DOMAIN; import static org.apache.james.jmap.JMAPTestingConstants.jmapRequestSpecBuilder; -import static org.apache.james.jmap.JmapCommonRequests.getDraftId; -import static org.apache.james.jmap.JmapCommonRequests.listMessageIdsForAccount; +import static org.apache.james.jmap.JmapRFCCommonRequests.UserCredential; +import static org.apache.james.jmap.JmapRFCCommonRequests.getDraftId; +import static org.apache.james.jmap.JmapRFCCommonRequests.getUserCredential; +import static org.apache.james.jmap.JmapRFCCommonRequests.listMessageIdsForAccount; import static org.assertj.core.api.Assertions.assertThat; import java.time.Duration; @@ -41,8 +42,6 @@ import org.apache.james.JamesServerBuilder; import org.apache.james.JamesServerExtension; import org.apache.james.SearchConfiguration; import org.apache.james.events.RetryBackoffConfiguration; -import org.apache.james.jmap.AccessToken; -import org.apache.james.jmap.LocalHostURIBuilder; import org.apache.james.jmap.draft.JmapGuiceProbe; import org.apache.james.junit.categories.BasicFeature; import org.apache.james.modules.AwsS3BlobStoreExtension; @@ -106,7 +105,7 @@ class RabbitMQReindexingWithEventDeadLettersTest { .build(); private RequestSpecification webAdminApi; - private AccessToken aliceAccessToken; + private UserCredential aliceCredential; @BeforeEach void setUp(GuiceJamesServer jamesServer) throws Exception { @@ -123,7 +122,7 @@ class RabbitMQReindexingWithEventDeadLettersTest { webAdminApi = WebAdminUtils.spec(jamesServer.getProbe(WebAdminGuiceProbe.class).getWebAdminPort()); - aliceAccessToken = authenticateJamesUser(LocalHostURIBuilder.baseUri(jmapPort), ALICE, ALICE_PASSWORD); + aliceCredential = getUserCredential(ALICE, ALICE_PASSWORD); dockerOpenSearch.getDockerOS().pause(); Thread.sleep(Duration.ofSeconds(2).toMillis()); // Docker pause is asynchronous and we found no way to poll for it @@ -137,7 +136,7 @@ class RabbitMQReindexingWithEventDeadLettersTest { CALMLY_AWAIT.until(() -> listOpenSearchFailedEvents().size() == 1); unpauseOpenSearch(); - assertThat(listMessageIdsForAccount(aliceAccessToken)).isEmpty(); + assertThat(listMessageIdsForAccount(aliceCredential)).isEmpty(); } @Test @@ -148,7 +147,7 @@ class RabbitMQReindexingWithEventDeadLettersTest { unpauseOpenSearch(); redeliverAllFailedEvents(); - CALMLY_AWAIT.until(() -> listMessageIdsForAccount(aliceAccessToken).size() == 1); + CALMLY_AWAIT.until(() -> listMessageIdsForAccount(aliceCredential).size() == 1); assertThat(listOpenSearchFailedEvents()).isEmpty(); } @@ -158,25 +157,28 @@ class RabbitMQReindexingWithEventDeadLettersTest { } private void aliceSavesADraft() { - String messageCreationId = "creationId1337"; - String requestBody = "[" + - " [" + - " \"setMessages\"," + - " {" + - " \"create\": { \"" + messageCreationId + "\" : {" + - " \"from\": { \"name\": \"Me\", \"email\": \"" + ALICE.asString() + "\"}," + - " \"to\": [{ \"name\": \"BOB\", \"email\": \"[email protected]\"}]," + - " \"subject\": \"subject\"," + - " \"keywords\": {\"$Draft\": true}," + - " \"mailboxIds\": [\"" + getDraftId(aliceAccessToken) + "\"]" + - " }}" + - " }," + - " \"#0\"" + - " ]" + - "]"; + String draftMailboxId = getDraftId(aliceCredential); + String requestBody = + "{" + + " \"using\": [\"urn:ietf:params:jmap:core\", \"urn:ietf:params:jmap:mail\"]," + + " \"methodCalls\": [" + + " [\"Email/set\", {" + + " \"accountId\": \"" + aliceCredential.accountId() + "\"," + + " \"create\": {" + + " \"e1526\": {" + + " \"mailboxIds\": { \"" + draftMailboxId + "\": true }," + + " \"subject\": \"subject\"," + + " \"keywords\": {\"$Draft\": true}," + + " \"to\": [{\"email\": \"[email protected]\"}]," + + " \"from\": [{\"email\": \"" + ALICE.asString() + "\"}]" + + " }" + + " }" + + " }, \"c1\"]" + + " ]" + + "}"; with() - .header("Authorization", aliceAccessToken.asString()) + .auth().basic(aliceCredential.username().asString(), aliceCredential.password()) .body(requestBody) .post("/jmap"); } diff --git a/server/protocols/webadmin-integration-test/webadmin-integration-test-common/src/main/java/org/apache/james/webadmin/integration/FastViewProjectionHealthCheckIntegrationContract.java b/server/protocols/webadmin-integration-test/webadmin-integration-test-common/src/main/java/org/apache/james/webadmin/integration/FastViewProjectionHealthCheckIntegrationContract.java index 24acfa192d..21f1fdfbc5 100644 --- a/server/protocols/webadmin-integration-test/webadmin-integration-test-common/src/main/java/org/apache/james/webadmin/integration/FastViewProjectionHealthCheckIntegrationContract.java +++ b/server/protocols/webadmin-integration-test/webadmin-integration-test-common/src/main/java/org/apache/james/webadmin/integration/FastViewProjectionHealthCheckIntegrationContract.java @@ -20,30 +20,29 @@ package org.apache.james.webadmin.integration; import static io.restassured.RestAssured.with; -import static org.apache.james.jmap.HttpJmapAuthentication.authenticateJamesUser; import static org.apache.james.jmap.JMAPTestingConstants.ALICE; import static org.apache.james.jmap.JMAPTestingConstants.ALICE_PASSWORD; -import static org.apache.james.jmap.JMAPTestingConstants.ARGUMENTS; import static org.apache.james.jmap.JMAPTestingConstants.BOB; import static org.apache.james.jmap.JMAPTestingConstants.BOB_PASSWORD; import static org.apache.james.jmap.JMAPTestingConstants.CEDRIC; import static org.apache.james.jmap.JMAPTestingConstants.CEDRIC_PASSWORD; import static org.apache.james.jmap.JMAPTestingConstants.DOMAIN; -import static org.apache.james.jmap.JMAPTestingConstants.calmlyAwait; import static org.apache.james.jmap.JMAPTestingConstants.jmapRequestSpecBuilder; -import static org.apache.james.jmap.JmapCommonRequests.bodyOfMessage; -import static org.apache.james.jmap.JmapCommonRequests.getLastMessageId; -import static org.apache.james.jmap.JmapCommonRequests.getOutboxId; -import static org.apache.james.jmap.JmapCommonRequests.listMessageIdsForAccount; -import static org.apache.james.jmap.LocalHostURIBuilder.baseUri; +import static org.apache.james.jmap.JmapRFCCommonRequests.ACCEPT_JMAP_RFC_HEADER; +import static org.apache.james.jmap.JmapRFCCommonRequests.UserCredential; +import static org.apache.james.jmap.JmapRFCCommonRequests.getLastMessageId; +import static org.apache.james.jmap.JmapRFCCommonRequests.getUserCredential; +import static org.apache.james.jmap.JmapRFCCommonRequests.listMessageIdsForAccount; +import static org.apache.james.webadmin.integration.TestFixture.WAIT_THIRTY_SECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.notNullValue; import java.util.stream.IntStream; import org.apache.james.GuiceJamesServer; import org.apache.james.core.healthcheck.ResultStatus; -import org.apache.james.jmap.AccessToken; +import org.apache.james.jmap.JmapRFCCommonRequests; import org.apache.james.jmap.draft.JmapGuiceProbe; import org.apache.james.probe.DataProbe; import org.apache.james.util.Port; @@ -51,18 +50,20 @@ import org.apache.james.utils.DataProbeImpl; import org.apache.james.utils.WebAdminGuiceProbe; import org.apache.james.webadmin.WebAdminUtils; import org.eclipse.jetty.http.HttpStatus; +import org.hamcrest.Matchers; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import io.restassured.RestAssured; +import io.restassured.http.ContentType; import io.restassured.specification.RequestSpecification; public abstract class FastViewProjectionHealthCheckIntegrationContract { private static final String MESSAGE_FAST_VIEW_PROJECTION = "MessageFastViewProjection"; private RequestSpecification webAdminApi; - private AccessToken bobAccessToken; - private AccessToken aliceAccessToken; + private UserCredential bobCredential; + private UserCredential aliceCredential; @BeforeEach void setUp(GuiceJamesServer jamesServer) throws Exception { @@ -76,9 +77,8 @@ public abstract class FastViewProjectionHealthCheckIntegrationContract { RestAssured.requestSpecification = jmapRequestSpecBuilder .setPort(jmapPort.getValue()) .build(); - - bobAccessToken = authenticateJamesUser(baseUri(jmapPort), BOB, BOB_PASSWORD); - aliceAccessToken = authenticateJamesUser(baseUri(jmapPort), ALICE, ALICE_PASSWORD); + bobCredential = getUserCredential(BOB, BOB_PASSWORD); + aliceCredential = getUserCredential(ALICE, ALICE_PASSWORD); webAdminApi = WebAdminUtils.spec(jamesServer.getProbe(WebAdminGuiceProbe.class).getWebAdminPort()); } @@ -96,7 +96,7 @@ public abstract class FastViewProjectionHealthCheckIntegrationContract { @Test void checkShouldReturnHealthyAfterSendingAMessageWithReads() { - bobSendAMessageToAlice(); + bobSendAnEmailToAlice(); IntStream.rangeClosed(1, 20) .forEach(counter -> aliceReadLastMessage()); @@ -112,7 +112,7 @@ public abstract class FastViewProjectionHealthCheckIntegrationContract { @Test void checkShouldReturnDegradedAfterFewReadsOnAMissedProjection(GuiceJamesServer guiceJamesServer) throws Exception { - bobSendAMessageToAlice(); + bobSendAnEmailToAlice(); makeHealthCheckDegraded(guiceJamesServer); @@ -130,7 +130,7 @@ public abstract class FastViewProjectionHealthCheckIntegrationContract { @Test void checkShouldTurnFromDegradedToHealthyAfterMoreReadsOnAMissedProjection(GuiceJamesServer guiceJamesServer) { - bobSendAMessageToAlice(); + bobSendAnEmailToAlice(); makeHealthCheckDegraded(guiceJamesServer); @@ -152,39 +152,89 @@ public abstract class FastViewProjectionHealthCheckIntegrationContract { aliceReadLastMessage(); } - private void bobSendAMessageToAlice() { - String messageCreationId = "creationId"; - String outboxId = getOutboxId(bobAccessToken); - String requestBody = "[" + - " [" + - " \"setMessages\"," + - " {" + - " \"create\": { \"" + messageCreationId + "\" : {" + - " \"from\": { \"name\": \"Bob\", \"email\": \"" + BOB.asString() + "\"}," + - " \"to\": [{ \"name\": \"Alice\", \"email\": \"" + ALICE.asString() + "\"}]," + - " \"subject\": \"bob to alice\"," + - " \"textBody\": \"body\"," + - " \"htmlBody\": \"Test <b>body</b>, HTML version\"," + - " \"mailboxIds\": [\"" + outboxId + "\"] " + - " }}" + - " }," + - " \"#0\"" + - " ]" + - "]"; + private void bobSendAnEmailToAlice() { + JmapRFCCommonRequests.UserCredential bobCredential = getUserCredential(BOB, BOB_PASSWORD); + String bobOutboxId = JmapRFCCommonRequests.getOutboxId(bobCredential); + String request = + "{" + + " \"using\": [\"urn:ietf:params:jmap:core\", \"urn:ietf:params:jmap:mail\", \"urn:ietf:params:jmap:submission\"]," + + " \"methodCalls\": [" + + " [\"Email/set\", {" + + " \"accountId\": \"" + bobCredential.accountId() + "\"," + + " \"create\": {" + + " \"e1526\": {" + + " \"mailboxIds\": { \"" + bobOutboxId + "\": true }," + + " \"subject\": \"subject\"," + + " \"htmlBody\": [{" + + " \"partId\": \"a49d\"," + + " \"type\": \"text/html\"" + + " }]," + + " \"bodyValues\": {" + + " \"a49d\": {" + + " \"value\": \"Test <b>body</b>, HTML version\"" + + " }" + + " }," + + " \"to\": [{\"email\": \"" + ALICE.asString() + "\"}]," + + " \"from\": [{\"email\": \"" + BOB.asString() + "\"}]" + + " }" + + " }" + + " }, \"c1\"]," + + " [\"EmailSubmission/set\", {" + + " \"accountId\": \"" + bobCredential.accountId() + "\"," + + " \"create\": {" + + " \"k1490\": {" + + " \"emailId\": \"#e1526\"," + + " \"envelope\": {" + + " \"mailFrom\": {\"email\": \"" + BOB.asString() + "\"}," + + " \"rcptTo\": [{\"email\": \"" + ALICE.asString() + "\"}]" + + " }" + + " }" + + " }" + + " }, \"c3\"]" + + " ]" + + "}"; + with() - .header("Authorization", bobAccessToken.asString()) - .body(requestBody) + .auth().basic(bobCredential.username().asString(), bobCredential.password()) + .header(ACCEPT_JMAP_RFC_HEADER) + .body(request) .post("/jmap") .then() - .extract() - .body() - .path(ARGUMENTS + ".created." + messageCreationId + ".id"); + .statusCode(200) + .contentType(ContentType.JSON) + .body("methodResponses[0][1].created.e1526", Matchers.is(notNullValue())); - calmlyAwait.untilAsserted(() -> assertThat(listMessageIdsForAccount(aliceAccessToken)) - .hasSize(1)); + WAIT_THIRTY_SECONDS + .untilAsserted(() -> assertThat(listMessageIdsForAccount(aliceCredential)).hasSize(1)); } private void aliceReadLastMessage() { - bodyOfMessage(aliceAccessToken, getLastMessageId(aliceAccessToken)); + // read with fast view + with() + .auth().basic(aliceCredential.username().asString(), aliceCredential.password()) + .header(ACCEPT_JMAP_RFC_HEADER) + .body(""" + { + "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ], + "methodCalls": [ + [ + "Email/get", + { + "accountId": "%s", + "ids": ["%s"], + "properties": [ "preview", "hasAttachment" ] + }, + "c1" + ] + ] + } + """.formatted(aliceCredential.accountId(), getLastMessageId(aliceCredential))) + .when() + .post("/jmap") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .extract() + .jsonPath(); } } diff --git a/server/protocols/webadmin-integration-test/webadmin-integration-test-common/src/main/java/org/apache/james/webadmin/integration/ForwardIntegrationTest.java b/server/protocols/webadmin-integration-test/webadmin-integration-test-common/src/main/java/org/apache/james/webadmin/integration/ForwardIntegrationTest.java index c3884586ad..2f7a22bd9b 100644 --- a/server/protocols/webadmin-integration-test/webadmin-integration-test-common/src/main/java/org/apache/james/webadmin/integration/ForwardIntegrationTest.java +++ b/server/protocols/webadmin-integration-test/webadmin-integration-test-common/src/main/java/org/apache/james/webadmin/integration/ForwardIntegrationTest.java @@ -19,34 +19,32 @@ package org.apache.james.webadmin.integration; -import static io.restassured.RestAssured.given; import static io.restassured.RestAssured.with; -import static org.apache.james.jmap.HttpJmapAuthentication.authenticateJamesUser; import static org.apache.james.jmap.JMAPTestingConstants.ALICE; import static org.apache.james.jmap.JMAPTestingConstants.ALICE_PASSWORD; -import static org.apache.james.jmap.JMAPTestingConstants.ARGUMENTS; import static org.apache.james.jmap.JMAPTestingConstants.BOB; import static org.apache.james.jmap.JMAPTestingConstants.BOB_PASSWORD; import static org.apache.james.jmap.JMAPTestingConstants.CEDRIC; import static org.apache.james.jmap.JMAPTestingConstants.CEDRIC_PASSWORD; import static org.apache.james.jmap.JMAPTestingConstants.DOMAIN; -import static org.apache.james.jmap.JMAPTestingConstants.NAME; -import static org.apache.james.jmap.JMAPTestingConstants.calmlyAwait; import static org.apache.james.jmap.JMAPTestingConstants.jmapRequestSpecBuilder; -import static org.apache.james.jmap.JmapCommonRequests.getOutboxId; -import static org.apache.james.jmap.JmapCommonRequests.isAnyMessageFoundInRecipientsMailboxes; -import static org.apache.james.jmap.LocalHostURIBuilder.baseUri; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.hasSize; +import static org.apache.james.jmap.JmapRFCCommonRequests.ACCEPT_JMAP_RFC_HEADER; +import static org.apache.james.jmap.JmapRFCCommonRequests.UserCredential; +import static org.apache.james.jmap.JmapRFCCommonRequests.getUserCredential; +import static org.apache.james.jmap.JmapRFCCommonRequests.listMessageIdsForAccount; +import static org.apache.james.webadmin.integration.TestFixture.WAIT_THIRTY_SECONDS; +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; +import static org.hamcrest.Matchers.notNullValue; import java.io.IOException; -import java.time.Duration; -import java.util.concurrent.TimeUnit; import org.apache.james.GuiceJamesServer; -import org.apache.james.jmap.AccessToken; +import org.apache.james.jmap.JmapRFCCommonRequests; import org.apache.james.jmap.draft.JmapGuiceProbe; import org.apache.james.junit.categories.BasicFeature; +import org.apache.james.mailbox.DefaultMailboxes; +import org.apache.james.mailbox.probe.MailboxProbe; +import org.apache.james.modules.MailboxProbeImpl; import org.apache.james.modules.protocols.SmtpGuiceProbe; import org.apache.james.probe.DataProbe; import org.apache.james.util.Port; @@ -54,12 +52,14 @@ import org.apache.james.utils.DataProbeImpl; import org.apache.james.utils.SMTPMessageSender; import org.apache.james.utils.WebAdminGuiceProbe; import org.apache.james.webadmin.WebAdminUtils; +import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import io.restassured.RestAssured; +import io.restassured.http.ContentType; import io.restassured.specification.RequestSpecification; public abstract class ForwardIntegrationTest { @@ -70,16 +70,21 @@ public abstract class ForwardIntegrationTest { @BeforeEach void setUp(GuiceJamesServer jmapServer) throws Exception { + MailboxProbe mailboxProbe = jmapServer.getProbe(MailboxProbeImpl.class); + messageSender = new SMTPMessageSender(DOMAIN); DataProbe dataProbe = jmapServer.getProbe(DataProbeImpl.class); dataProbe.addDomain(DOMAIN); dataProbe.addUser(BOB.asString(), BOB_PASSWORD); dataProbe.addUser(ALICE.asString(), ALICE_PASSWORD); dataProbe.addUser(CEDRIC.asString(), CEDRIC_PASSWORD); + mailboxProbe.createMailbox("#private", ALICE.asString(), DefaultMailboxes.INBOX); + mailboxProbe.createMailbox("#private", CEDRIC.asString(), DefaultMailboxes.INBOX); jmapPort = jmapServer.getProbe(JmapGuiceProbe.class).getJmapPort(); RestAssured.requestSpecification = jmapRequestSpecBuilder .setPort(jmapPort.getValue()) + .addHeader(ACCEPT_JMAP_RFC_HEADER.getName(), ACCEPT_JMAP_RFC_HEADER.getValue()) .build(); webAdminApi = WebAdminUtils.spec(jmapServer.getProbe(WebAdminGuiceProbe.class).getWebAdminPort()); @@ -96,47 +101,11 @@ public abstract class ForwardIntegrationTest { void messageShouldBeForwardedWhenDefinedInRESTAPI() { webAdminApi.put(String.format("/address/forwards/%s/targets/%s", ALICE.asString(), BOB.asString())); - AccessToken cedricAccessToken = authenticateJamesUser(baseUri(jmapPort), CEDRIC, CEDRIC_PASSWORD); - String messageCreationId = "creationId1337"; - String requestBody = "[" + - " [" + - " \"setMessages\"," + - " {" + - " \"create\": { \"" + messageCreationId + "\" : {" + - " \"from\": { \"name\": \"Me\", \"email\": \"" + CEDRIC.asString() + "\"}," + - " \"to\": [{ \"name\": \"Alice\", \"email\": \"" + ALICE.asString() + "\"}]," + - " \"subject\": \"subject\"," + - " \"isUnread\": true," + - " \"isFlagged\": true," + - " \"isAnswered\": true," + - " \"isDraft\": true," + - " \"isForwarded\": true," + - " \"mailboxIds\": [\"" + getOutboxId(cedricAccessToken) + "\"]" + - " }}" + - " }," + - " \"#0\"" + - " ]" + - "]"; + cedricSendAnEmailToAlice(); - with() - .header("Authorization", cedricAccessToken.asString()) - .body(requestBody) - .post("/jmap"); - - AccessToken bobAccessToken = authenticateJamesUser(baseUri(jmapPort), BOB, BOB_PASSWORD); - calmlyAwait - .pollDelay(Duration.ofMillis(500)) - .atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInRecipientsMailboxes(bobAccessToken)); - given() - .header("Authorization", bobAccessToken.asString()) - .body("[[\"getMessageList\", {}, \"#0\"]]") - .when() - .post("/jmap") - .then() - .log().ifValidationFails() - .statusCode(200) - .body(NAME, equalTo("messageList")) - .body(ARGUMENTS + ".messageIds", hasSize(1)); + UserCredential bobCredential = getUserCredential(BOB, BOB_PASSWORD); + WAIT_THIRTY_SECONDS.untilAsserted(() -> assertThat(listMessageIdsForAccount(bobCredential)) + .hasSize(1)); } @Test @@ -144,59 +113,15 @@ public abstract class ForwardIntegrationTest { webAdminApi.put(String.format("/address/forwards/%s/targets/%s", ALICE.asString(), BOB.asString())); webAdminApi.put(String.format("/address/forwards/%s/targets/%s", ALICE.asString(), ALICE.asString())); - AccessToken cedricAccessToken = authenticateJamesUser(baseUri(jmapPort), CEDRIC, CEDRIC_PASSWORD); - AccessToken aliceAccessToken = authenticateJamesUser(baseUri(jmapPort), ALICE, ALICE_PASSWORD); - String messageCreationId = "creationId1337"; - String requestBody = "[" + - " [" + - " \"setMessages\"," + - " {" + - " \"create\": { \"" + messageCreationId + "\" : {" + - " \"from\": { \"name\": \"Me\", \"email\": \"" + CEDRIC.asString() + "\"}," + - " \"to\": [{ \"name\": \"Alice\", \"email\": \"" + ALICE.asString() + "\"}]," + - " \"subject\": \"subject\"," + - " \"isUnread\": true," + - " \"isFlagged\": true," + - " \"isAnswered\": true," + - " \"isDraft\": true," + - " \"isForwarded\": true," + - " \"mailboxIds\": [\"" + getOutboxId(cedricAccessToken) + "\"]" + - " }}" + - " }," + - " \"#0\"" + - " ]" + - "]"; + cedricSendAnEmailToAlice(); - with() - .header("Authorization", cedricAccessToken.asString()) - .body(requestBody) - .post("/jmap"); - - AccessToken bobAccessToken = authenticateJamesUser(baseUri(jmapPort), BOB, BOB_PASSWORD); - calmlyAwait - .pollDelay(Duration.ofMillis(500)) - .atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInRecipientsMailboxes(bobAccessToken)); - given() - .header("Authorization", bobAccessToken.asString()) - .body("[[\"getMessageList\", {}, \"#0\"]]") - .when() - .post("/jmap") - .then() - .log().ifValidationFails() - .statusCode(200) - .body(NAME, equalTo("messageList")) - .body(ARGUMENTS + ".messageIds", hasSize(1)); + UserCredential bobCredential = getUserCredential(BOB, BOB_PASSWORD); + WAIT_THIRTY_SECONDS.untilAsserted(() -> assertThat(listMessageIdsForAccount(bobCredential)) + .hasSize(1)); - given() - .header("Authorization", aliceAccessToken.asString()) - .body("[[\"getMessageList\", {}, \"#0\"]]") - .when() - .post("/jmap") - .then() - .log().ifValidationFails() - .statusCode(200) - .body(NAME, equalTo("messageList")) - .body(ARGUMENTS + ".messageIds", hasSize(1)); + UserCredential aliceCredential = getUserCredential(BOB, BOB_PASSWORD); + WAIT_THIRTY_SECONDS.untilAsserted(() -> assertThat(listMessageIdsForAccount(aliceCredential)) + .hasSize(1)); } @Test @@ -208,32 +133,13 @@ public abstract class ForwardIntegrationTest { .connect("127.0.0.1", jmapServer.getProbe(SmtpGuiceProbe.class).getSmtpPort()) .sendMessage("[email protected]", ALICE.asString()); - AccessToken bobAccessToken = authenticateJamesUser(baseUri(jmapPort), BOB, BOB_PASSWORD); - calmlyAwait - .pollDelay(Duration.ofMillis(500)) - .atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInRecipientsMailboxes(bobAccessToken)); - given() - .header("Authorization", bobAccessToken.asString()) - .body("[[\"getMessageList\", {}, \"#0\"]]") - .when() - .post("/jmap") - .then() - .log().ifValidationFails() - .statusCode(200) - .body(NAME, equalTo("messageList")) - .body(ARGUMENTS + ".messageIds", hasSize(1)); - - AccessToken aliceAccessToken = authenticateJamesUser(baseUri(jmapPort), ALICE, ALICE_PASSWORD); - given() - .header("Authorization", aliceAccessToken.asString()) - .body("[[\"getMessageList\", {}, \"#0\"]]") - .when() - .post("/jmap") - .then() - .log().ifValidationFails() - .statusCode(200) - .body(NAME, equalTo("messageList")) - .body(ARGUMENTS + ".messageIds", hasSize(1)); + UserCredential bobCredential = getUserCredential(BOB, BOB_PASSWORD); + WAIT_THIRTY_SECONDS.untilAsserted(() -> assertThat(listMessageIdsForAccount(bobCredential)) + .hasSize(1)); + + UserCredential aliceCredential = getUserCredential(BOB, BOB_PASSWORD); + WAIT_THIRTY_SECONDS.untilAsserted(() -> assertThat(listMessageIdsForAccount(aliceCredential)) + .hasSize(1)); } @Test @@ -241,47 +147,11 @@ public abstract class ForwardIntegrationTest { webAdminApi.put(String.format("/address/forwards/%s/targets/%s", ALICE.asString(), CEDRIC.asString())); webAdminApi.put(String.format("/address/forwards/%s/targets/%s", CEDRIC.asString(), BOB.asString())); - AccessToken cedricAccessToken = authenticateJamesUser(baseUri(jmapPort), CEDRIC, CEDRIC_PASSWORD); - String messageCreationId = "creationId1337"; - String requestBody = "[" + - " [" + - " \"setMessages\"," + - " {" + - " \"create\": { \"" + messageCreationId + "\" : {" + - " \"from\": { \"name\": \"Me\", \"email\": \"" + CEDRIC.asString() + "\"}," + - " \"to\": [{ \"name\": \"Alice\", \"email\": \"" + ALICE.asString() + "\"}]," + - " \"subject\": \"subject\"," + - " \"isUnread\": true," + - " \"isFlagged\": true," + - " \"isAnswered\": true," + - " \"isDraft\": true," + - " \"isForwarded\": true," + - " \"mailboxIds\": [\"" + getOutboxId(cedricAccessToken) + "\"]" + - " }}" + - " }," + - " \"#0\"" + - " ]" + - "]"; + cedricSendAnEmailToAlice(); - with() - .header("Authorization", cedricAccessToken.asString()) - .body(requestBody) - .post("/jmap"); - - AccessToken bobAccessToken = authenticateJamesUser(baseUri(jmapPort), BOB, BOB_PASSWORD); - calmlyAwait - .pollDelay(Duration.ofMillis(500)) - .atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInRecipientsMailboxes(bobAccessToken)); - given() - .header("Authorization", bobAccessToken.asString()) - .body("[[\"getMessageList\", {}, \"#0\"]]") - .when() - .post("/jmap") - .then() - .log().ifValidationFails() - .statusCode(200) - .body(NAME, equalTo("messageList")) - .body(ARGUMENTS + ".messageIds", hasSize(1)); + UserCredential bobCredential = getUserCredential(BOB, BOB_PASSWORD); + WAIT_THIRTY_SECONDS.untilAsserted(() -> assertThat(listMessageIdsForAccount(bobCredential)) + .hasSize(1)); } @Test @@ -290,96 +160,78 @@ public abstract class ForwardIntegrationTest { webAdminApi.put(String.format("/address/forwards/%s/targets/%s", ALICE.asString(), BOB.asString())); webAdminApi.put(String.format("/address/forwards/%s/targets/%s", BOB.asString(), CEDRIC.asString())); - AccessToken cedricAccessToken = authenticateJamesUser(baseUri(jmapPort), CEDRIC, CEDRIC_PASSWORD); - AccessToken aliceAccessToken = authenticateJamesUser(baseUri(jmapPort), ALICE, ALICE_PASSWORD); - String messageCreationId = "creationId1337"; - String requestBody = "[" + - " [" + - " \"setMessages\"," + - " {" + - " \"create\": { \"" + messageCreationId + "\" : {" + - " \"from\": { \"name\": \"Me\", \"email\": \"" + CEDRIC.asString() + "\"}," + - " \"to\": [{ \"name\": \"Alice\", \"email\": \"" + ALICE.asString() + "\"}]," + - " \"subject\": \"subject\"," + - " \"isUnread\": true," + - " \"isFlagged\": true," + - " \"isAnswered\": true," + - " \"isDraft\": true," + - " \"isForwarded\": true," + - " \"mailboxIds\": [\"" + getOutboxId(cedricAccessToken) + "\"]" + - " }}" + - " }," + - " \"#0\"" + - " ]" + - "]"; + cedricSendAnEmailToAlice(); - with() - .header("Authorization", cedricAccessToken.asString()) - .body(requestBody) - .post("/jmap"); - - calmlyAwait - .pollDelay(Duration.ofMillis(500)) - .atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInRecipientsMailboxes(aliceAccessToken)); - given() - .header("Authorization", aliceAccessToken.asString()) - .body("[[\"getMessageList\", {}, \"#0\"]]") - .when() - .post("/jmap") - .then() - .log().ifValidationFails() - .statusCode(200) - .body(NAME, equalTo("messageList")) - .body(ARGUMENTS + ".messageIds", hasSize(1)); + UserCredential aliceCredential = getUserCredential(ALICE, ALICE_PASSWORD); + WAIT_THIRTY_SECONDS.untilAsserted(() -> assertThat(listMessageIdsForAccount(aliceCredential)) + .hasSize(1)); } @Test void baseRecipientShouldNotReceiveEmailOnDefaultForward() { webAdminApi.put(String.format("/address/forwards/%s/targets/%s", ALICE.asString(), BOB.asString())); - AccessToken cedricAccessToken = authenticateJamesUser(baseUri(jmapPort), CEDRIC, CEDRIC_PASSWORD); - AccessToken aliceAccessToken = authenticateJamesUser(baseUri(jmapPort), ALICE, ALICE_PASSWORD); - String messageCreationId = "creationId1337"; - String requestBody = "[" + - " [" + - " \"setMessages\"," + - " {" + - " \"create\": { \"" + messageCreationId + "\" : {" + - " \"from\": { \"name\": \"Me\", \"email\": \"" + CEDRIC.asString() + "\"}," + - " \"to\": [{ \"name\": \"Alice\", \"email\": \"" + ALICE.asString() + "\"}]," + - " \"subject\": \"subject\"," + - " \"isUnread\": true," + - " \"isFlagged\": true," + - " \"isAnswered\": true," + - " \"isDraft\": true," + - " \"isForwarded\": true," + - " \"mailboxIds\": [\"" + getOutboxId(cedricAccessToken) + "\"]" + - " }}" + - " }," + - " \"#0\"" + - " ]" + - "]"; + cedricSendAnEmailToAlice(); + + UserCredential bobCredential = getUserCredential(BOB, BOB_PASSWORD); + WAIT_THIRTY_SECONDS.untilAsserted(() -> assertThat(listMessageIdsForAccount(bobCredential)) + .hasSize(1)); + + UserCredential aliceCredential = getUserCredential(ALICE, ALICE_PASSWORD); + assertThat(listMessageIdsForAccount(aliceCredential)) + .hasSize(0); + } + + private void cedricSendAnEmailToAlice() { + UserCredential cedricCredential = getUserCredential(CEDRIC, CEDRIC_PASSWORD); + String cedricOutboxId = JmapRFCCommonRequests.getOutboxId(cedricCredential); + String request = + "{" + + " \"using\": [\"urn:ietf:params:jmap:core\", \"urn:ietf:params:jmap:mail\", \"urn:ietf:params:jmap:submission\"]," + + " \"methodCalls\": [" + + " [\"Email/set\", {" + + " \"accountId\": \"" + cedricCredential.accountId() + "\"," + + " \"create\": {" + + " \"e1526\": {" + + " \"mailboxIds\": { \"" + cedricOutboxId + "\": true }," + + " \"subject\": \"subject\"," + + " \"htmlBody\": [{" + + " \"partId\": \"a49d\"," + + " \"type\": \"text/html\"" + + " }]," + + " \"bodyValues\": {" + + " \"a49d\": {" + + " \"value\": \"Test <b>body</b>, HTML version\"" + + " }" + + " }," + + " \"to\": [{\"email\": \"" + ALICE.asString() + "\"}]," + + " \"from\": [{\"email\": \"" + CEDRIC.asString() + "\"}]" + + " }" + + " }" + + " }, \"c1\"]," + + " [\"EmailSubmission/set\", {" + + " \"accountId\": \"" + cedricCredential.accountId() + "\"," + + " \"create\": {" + + " \"k1490\": {" + + " \"emailId\": \"#e1526\"," + + " \"envelope\": {" + + " \"mailFrom\": {\"email\": \"" + CEDRIC.asString() + "\"}," + + " \"rcptTo\": [{\"email\": \"" + ALICE.asString() + "\"}]" + + " }" + + " }" + + " }" + + " }, \"c3\"]" + + " ]" + + "}"; with() - .header("Authorization", cedricAccessToken.asString()) - .body(requestBody) - .post("/jmap"); - - AccessToken bobAccessToken = authenticateJamesUser(baseUri(jmapPort), BOB, BOB_PASSWORD); - calmlyAwait - .pollDelay(Duration.ofMillis(500)) - .atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInRecipientsMailboxes(bobAccessToken)); - - given() - .header("Authorization", aliceAccessToken.asString()) - .body("[[\"getMessageList\", {}, \"#0\"]]") - .when() + .auth().basic(cedricCredential.username().asString(), cedricCredential.password()) + .body(request) .post("/jmap") .then() - .log().ifValidationFails() .statusCode(200) - .body(NAME, equalTo("messageList")) - .body(ARGUMENTS + ".messageIds", hasSize(0)); + .contentType(ContentType.JSON) + .body("methodResponses[0][1].created.e1526", Matchers.is(notNullValue())); } } diff --git a/server/protocols/webadmin-integration-test/webadmin-integration-test-common/src/main/java/org/apache/james/webadmin/integration/TestFixture.java b/server/protocols/webadmin-integration-test/webadmin-integration-test-common/src/main/java/org/apache/james/webadmin/integration/TestFixture.java new file mode 100644 index 0000000000..1502974376 --- /dev/null +++ b/server/protocols/webadmin-integration-test/webadmin-integration-test-common/src/main/java/org/apache/james/webadmin/integration/TestFixture.java @@ -0,0 +1,34 @@ +/**************************************************************** + * 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.integration; + +import static org.apache.james.jmap.JMAPTestingConstants.calmlyAwait; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +import org.awaitility.core.ConditionFactory; + +public interface TestFixture { + + ConditionFactory WAIT_THIRTY_SECONDS = calmlyAwait + .pollDelay(Duration.ofMillis(500)) + .atMost(30, TimeUnit.SECONDS); +} diff --git a/server/protocols/webadmin-integration-test/webadmin-integration-test-common/src/main/java/org/apache/james/webadmin/integration/vault/DeletedMessageVaultIntegrationTest.java b/server/protocols/webadmin-integration-test/webadmin-integration-test-common/src/main/java/org/apache/james/webadmin/integration/vault/DeletedMessageVaultIntegrationTest.java index 89695a57d3..4d5d0c5afc 100644 --- a/server/protocols/webadmin-integration-test/webadmin-integration-test-common/src/main/java/org/apache/james/webadmin/integration/vault/DeletedMessageVaultIntegrationTest.java +++ b/server/protocols/webadmin-integration-test/webadmin-integration-test-common/src/main/java/org/apache/james/webadmin/integration/vault/DeletedMessageVaultIntegrationTest.java @@ -22,19 +22,21 @@ package org.apache.james.webadmin.integration.vault; import static io.restassured.RestAssured.given; import static io.restassured.RestAssured.with; import static io.restassured.config.ParamConfig.UpdateStrategy.REPLACE; -import static org.apache.james.jmap.HttpJmapAuthentication.authenticateJamesUser; -import static org.apache.james.jmap.JMAPTestingConstants.ARGUMENTS; +import static io.restassured.http.ContentType.JSON; import static org.apache.james.jmap.JMAPTestingConstants.DOMAIN; import static org.apache.james.jmap.JMAPTestingConstants.LOCALHOST_IP; import static org.apache.james.jmap.JMAPTestingConstants.calmlyAwait; import static org.apache.james.jmap.JMAPTestingConstants.jmapRequestSpecBuilder; -import static org.apache.james.jmap.JmapCommonRequests.deleteMessages; -import static org.apache.james.jmap.JmapCommonRequests.getAllMailboxesIds; -import static org.apache.james.jmap.JmapCommonRequests.getLatestMessageId; -import static org.apache.james.jmap.JmapCommonRequests.getOutboxId; -import static org.apache.james.jmap.JmapCommonRequests.listMessageIdsForAccount; -import static org.apache.james.jmap.JmapCommonRequests.listMessageIdsInMailbox; -import static org.apache.james.jmap.LocalHostURIBuilder.baseUri; +import static org.apache.james.jmap.JmapRFCCommonRequests.ACCEPT_JMAP_RFC_HEADER; +import static org.apache.james.jmap.JmapRFCCommonRequests.UserCredential; +import static org.apache.james.jmap.JmapRFCCommonRequests.deleteMessages; +import static org.apache.james.jmap.JmapRFCCommonRequests.getAllMailboxesIds; +import static org.apache.james.jmap.JmapRFCCommonRequests.getLatestMessageId; +import static org.apache.james.jmap.JmapRFCCommonRequests.getMessageContent; +import static org.apache.james.jmap.JmapRFCCommonRequests.getOutboxId; +import static org.apache.james.jmap.JmapRFCCommonRequests.getUserCredential; +import static org.apache.james.jmap.JmapRFCCommonRequests.listMessageIdsForAccount; +import static org.apache.james.jmap.JmapRFCCommonRequests.listMessageIdsInMailbox; import static org.apache.james.mailbox.backup.ZipAssert.EntryChecks.hasName; import static org.apache.james.mailbox.backup.ZipAssert.assertThatZip; import static org.apache.james.webadmin.integration.vault.DeletedMessagesVaultRequests.deleteFromVault; @@ -44,18 +46,17 @@ import static org.apache.james.webadmin.integration.vault.DeletedMessagesVaultRe import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Durations.FIVE_SECONDS; import static org.awaitility.Durations.TWO_MINUTES; -import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.notNullValue; import java.io.FileInputStream; import java.io.IOException; import java.time.Clock; import java.time.ZonedDateTime; import java.util.List; +import java.util.Map; import org.apache.james.GuiceJamesServer; import org.apache.james.GuiceModuleTestExtension; -import org.apache.james.core.Username; -import org.apache.james.jmap.AccessToken; import org.apache.james.jmap.draft.JmapGuiceProbe; import org.apache.james.junit.categories.BasicFeature; import org.apache.james.mailbox.DefaultMailboxes; @@ -73,6 +74,7 @@ import org.apache.james.utils.UpdatableTickingClock; import org.apache.james.utils.WebAdminGuiceProbe; import org.apache.james.webadmin.WebAdminUtils; import org.awaitility.core.ConditionFactory; +import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; @@ -81,7 +83,6 @@ import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; -import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.inject.Module; @@ -142,12 +143,13 @@ public abstract class DeletedMessageVaultIntegrationTest { .query(MATCH_ALL_QUERY); private TestIMAPClient testIMAPClient; - private AccessToken homerAccessToken; - private AccessToken bartAccessToken; - private AccessToken jackAccessToken; private RequestSpecification webAdminApi; private MailboxId otherMailboxId; + private UserCredential homerCredential; + private UserCredential bartCredential; + private UserCredential jackCredential; + @BeforeEach void setup(GuiceJamesServer jmapServer) throws Throwable { MailboxProbe mailboxProbe = jmapServer.getProbe(MailboxProbeImpl.class); @@ -156,6 +158,7 @@ public abstract class DeletedMessageVaultIntegrationTest { Port jmapPort = jmapServer.getProbe(JmapGuiceProbe.class).getJmapPort(); RestAssured.requestSpecification = jmapRequestSpecBuilder .setPort(jmapPort.getValue()) + .addHeader(ACCEPT_JMAP_RFC_HEADER.getName(), ACCEPT_JMAP_RFC_HEADER.getValue()) .build(); RestAssured.defaultParser = Parser.JSON; @@ -165,9 +168,10 @@ public abstract class DeletedMessageVaultIntegrationTest { dataProbe.addUser(JACK, PASSWORD); mailboxProbe.createMailbox("#private", HOMER, DefaultMailboxes.INBOX); otherMailboxId = mailboxProbe.createMailbox("#private", HOMER, MAILBOX_NAME); - homerAccessToken = authenticateJamesUser(baseUri(jmapPort), Username.of(HOMER), PASSWORD); - bartAccessToken = authenticateJamesUser(baseUri(jmapPort), Username.of(BART), BOB_PASSWORD); - jackAccessToken = authenticateJamesUser(baseUri(jmapPort), Username.of(JACK), PASSWORD); + + homerCredential = getUserCredential(HOMER, PASSWORD); + bartCredential = getUserCredential(BART, BOB_PASSWORD); + jackCredential = getUserCredential(JACK, PASSWORD); testIMAPClient = new TestIMAPClient(); @@ -188,31 +192,24 @@ public abstract class DeletedMessageVaultIntegrationTest { @Test void vaultEndpointShouldRestoreJmapDeletedEmail() { bartSendMessageToHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); - homerDeletesMessages(listMessageIdsForAccount(homerAccessToken)); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(0)); + homerDeletesMessages(listMessageIdsForAccount(homerCredential)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0)); restoreAllMessagesOfHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); - String messageId = listMessageIdsForAccount(homerAccessToken).get(0); - given() - .header("Authorization", homerAccessToken.asString()) - .body("[[\"getMessages\", {\"ids\": [\"" + messageId + "\"]}, \"#0\"]]") - .when() - .post("/jmap") - .then() - .statusCode(200) - .log().ifValidationFails() - .body(ARGUMENTS + ".list.subject", hasItem(SUBJECT)); + String messageId = listMessageIdsForAccount(homerCredential).get(0); + assertThat(getMessageContent(homerCredential, messageId) + .getString("methodResponses[0][1].list[0].subject")).isEqualTo(SUBJECT); } @Tag(BasicFeature.TAG) @Test void vaultEndpointShouldRestoreImapDeletedEmail(GuiceJamesServer jmapServer) throws Exception { bartSendMessageToHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); testIMAPClient.connect(LOCALHOST_IP, jmapServer.getProbe(ImapGuiceProbe.class).getImapPort()) .login(HOMER, PASSWORD) @@ -220,66 +217,52 @@ public abstract class DeletedMessageVaultIntegrationTest { .setFlagsForAllMessagesInMailbox("\\Deleted"); testIMAPClient.expunge(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(0)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0)); restoreAllMessagesOfHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); - String messageId = listMessageIdsForAccount(homerAccessToken).get(0); - given() - .header("Authorization", homerAccessToken.asString()) - .body("[[\"getMessages\", {\"ids\": [\"" + messageId + "\"]}, \"#0\"]]") - .when() - .post("/jmap") - .then() - .statusCode(200) - .log().ifValidationFails() - .body(ARGUMENTS + ".list.subject", hasItem(SUBJECT)); + String messageId = listMessageIdsForAccount(homerCredential).get(0); + assertThat(getMessageContent(homerCredential, messageId) + .getString("methodResponses[0][1].list[0].subject")).isEqualTo(SUBJECT); } @Tag(BasicFeature.TAG) @Test void vaultEndpointShouldRestoreImapDeletedMailbox(GuiceJamesServer jmapServer) throws Exception { bartSendMessageToHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); testIMAPClient.connect(LOCALHOST_IP, jmapServer.getProbe(ImapGuiceProbe.class).getImapPort()) .login(HOMER, PASSWORD) .select(TestIMAPClient.INBOX); testIMAPClient.moveFirstMessage(MAILBOX_NAME); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsInMailbox(homerAccessToken, otherMailboxId.serialize())).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsInMailbox(homerCredential, otherMailboxId.serialize())).hasSize(1)); testIMAPClient.delete(MAILBOX_NAME); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(0)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0)); Thread.sleep(1000); // Wait for messages to be moved to the vault restoreAllMessagesOfHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); - String messageId = listMessageIdsForAccount(homerAccessToken).get(0); - given() - .header("Authorization", homerAccessToken.asString()) - .body("[[\"getMessages\", {\"ids\": [\"" + messageId + "\"]}, \"#0\"]]") - .when() - .post("/jmap") - .then() - .statusCode(200) - .log().ifValidationFails() - .body(ARGUMENTS + ".list.subject", hasItem(SUBJECT)); + String messageId = listMessageIdsForAccount(homerCredential).get(0); + assertThat(getMessageContent(homerCredential, messageId) + .getString("methodResponses[0][1].list[0].subject")).isEqualTo(SUBJECT); } @Test void restoreShouldCreateRestoreMessagesMailbox() { bartSendMessageToHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); - homerDeletesMessages(listMessageIdsForAccount(homerAccessToken)); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(0)); + homerDeletesMessages(listMessageIdsForAccount(homerCredential)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0)); restoreAllMessagesOfHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); assertThat(homerHasMailboxWithRole(Role.RESTORED_MESSAGES)).isTrue(); } @@ -288,11 +271,11 @@ public abstract class DeletedMessageVaultIntegrationTest { void postShouldRestoreMatchingMessages() { bartSendMessageToHomerWithSubject("aaaaa"); bartSendMessageToHomerWithSubject("bbbbb"); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(2)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(2)); - homerDeletesMessages(listMessageIdsForAccount(homerAccessToken)); + homerDeletesMessages(listMessageIdsForAccount(homerCredential)); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(0)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0)); String query = "{" + " \"combinator\": \"and\"," + @@ -306,29 +289,22 @@ public abstract class DeletedMessageVaultIntegrationTest { "}"; restoreMessagesForUserWithQuery(webAdminApi, HOMER, query); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); - String messageId = listMessageIdsForAccount(homerAccessToken).get(0); - given() - .header("Authorization", homerAccessToken.asString()) - .body("[[\"getMessages\", {\"ids\": [\"" + messageId + "\"]}, \"#0\"]]") - .when() - .post("/jmap") - .then() - .statusCode(200) - .log().ifValidationFails() - .body(ARGUMENTS + ".list.subject", hasItem("aaaaa")); + String messageId = listMessageIdsForAccount(homerCredential).get(0); + assertThat(getMessageContent(homerCredential, messageId) + .getString("methodResponses[0][1].list[0].subject")).isEqualTo("aaaaa"); } @Test void postShouldNotRestoreWhenNoMatchingMessages() throws Exception { bartSendMessageToHomerWithSubject("aaaaa"); bartSendMessageToHomerWithSubject("bbbbb"); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(2)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(2)); - homerDeletesMessages(listMessageIdsForAccount(homerAccessToken)); + homerDeletesMessages(listMessageIdsForAccount(homerCredential)); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(0)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0)); String query = "{" + " \"combinator\": \"and\"," + @@ -346,7 +322,7 @@ public abstract class DeletedMessageVaultIntegrationTest { Thread.sleep(FIVE_SECONDS.toMillis()); // No additional had been restored for Bart as the vault is empty - assertThat(listMessageIdsForAccount(homerAccessToken).size()) + assertThat(listMessageIdsForAccount(homerCredential).size()) .isEqualTo(0); } @@ -354,11 +330,11 @@ public abstract class DeletedMessageVaultIntegrationTest { void postShouldRestoreMatchingMessagesWhenQueryLimit() { bartSendMessageToHomerWithSubject("aaaa"); bartSendMessageToHomerWithSubject("aaaa"); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(2)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(2)); - homerDeletesMessages(listMessageIdsForAccount(homerAccessToken)); + homerDeletesMessages(listMessageIdsForAccount(homerCredential)); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(0)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0)); String query = "{" + " \"combinator\": \"and\"," + @@ -373,13 +349,13 @@ public abstract class DeletedMessageVaultIntegrationTest { "}"; restoreMessagesForUserWithQuery(webAdminApi, HOMER, query); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); } @Test void imapMovedMessageShouldNotEndUpInTheVault(GuiceJamesServer jmapServer) throws Exception { bartSendMessageToHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); testIMAPClient.connect(LOCALHOST_IP, jmapServer.getProbe(ImapGuiceProbe.class).getImapPort()) .login(HOMER, PASSWORD) @@ -389,137 +365,131 @@ public abstract class DeletedMessageVaultIntegrationTest { //Moved messages should not be moved to the vault restoreAllMessagesOfHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); // No messages restored for bart - assertThat(listMessageIdsForAccount(bartAccessToken).size()).isEqualTo(1); + assertThat(listMessageIdsForAccount(homerCredential).size()).isEqualTo(1); } @Test void jmapMovedMessageShouldNotEndUpInTheVault() { bartSendMessageToHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); - String messageId = listMessageIdsForAccount(homerAccessToken).get(0); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); + String messageId = listMessageIdsForAccount(homerCredential).get(0); homerMovesTheMailInAnotherMailbox(messageId); //Moved messages should not be moved to the vault restoreAllMessagesOfHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); // No messages restored for bart - assertThat(listMessageIdsForAccount(bartAccessToken).size()).isEqualTo(1); + assertThat(listMessageIdsForAccount(homerCredential).size()).isEqualTo(1); } @Test void restoreShouldNotImpactOtherUsers() { bartSendMessageToHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); - homerDeletesMessages(listMessageIdsForAccount(homerAccessToken)); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(0)); + homerDeletesMessages(listMessageIdsForAccount(homerCredential)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0)); - bartDeletesMessages(listMessageIdsForAccount(bartAccessToken)); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(bartAccessToken)).hasSize(0)); + bartDeletesMessages(listMessageIdsForAccount(bartCredential)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(bartCredential)).hasSize(0)); restoreAllMessagesOfHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); // No messages restored for bart - assertThat(listMessageIdsForAccount(bartAccessToken).size()).isEqualTo(0); + assertThat(listMessageIdsForAccount(bartCredential).size()).isEqualTo(0); } @Test void restoredMessagesShouldNotBeRemovedFromTheVault() { bartSendMessageToHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); - homerDeletesMessages(listMessageIdsForAccount(homerAccessToken)); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(0)); + homerDeletesMessages(listMessageIdsForAccount(homerCredential)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0)); restoreAllMessagesOfHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); restoreAllMessagesOfHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(2)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(2)); } @Test void vaultEndpointShouldNotRestoreItemsWhenTheVaultIsEmpty() { bartSendMessageToHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); restoreAllMessagesOfHomer(); awaitSearchUpToDate(); // No additional had been restored as the vault is empty - assertThat(listMessageIdsForAccount(homerAccessToken).size()) + assertThat(listMessageIdsForAccount(homerCredential).size()) .isEqualTo(1); } @Test void vaultEndpointShouldNotRestoreMessageForSharee() { bartSendMessageToHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(bartAccessToken)).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(bartCredential)).hasSize(1)); - String messageId = listMessageIdsForAccount(homerAccessToken).get(0); + String messageId = listMessageIdsForAccount(homerCredential).get(0); homerMovesTheMailInAnotherMailbox(messageId); homerSharesHisMailboxWithBart(); bartDeletesMessages(ImmutableList.of(messageId)); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(0)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0)); restoreMessagesFor(BART); awaitSearchUpToDate(); // No additional had been restored for Bart as the vault is empty - assertThat(listMessageIdsForAccount(bartAccessToken).size()) + assertThat(listMessageIdsForAccount(bartCredential).size()) .isEqualTo(1); } @Test void vaultEndpointShouldRestoreMessageForSharer() { bartSendMessageToHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); - String messageId = listMessageIdsForAccount(homerAccessToken).get(0); + String messageId = listMessageIdsForAccount(homerCredential).get(0); homerMovesTheMailInAnotherMailbox(messageId); homerSharesHisMailboxWithBart(); bartDeletesMessages(ImmutableList.of(messageId)); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(0)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0)); restoreAllMessagesOfHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); - String newMessageId = listMessageIdsForAccount(homerAccessToken).get(0); - given() - .header("Authorization", homerAccessToken.asString()) - .body("[[\"getMessages\", {\"ids\": [\"" + newMessageId + "\"]}, \"#0\"]]") - .when() - .post("/jmap") - .then() - .statusCode(200) - .log().ifValidationFails() - .body(ARGUMENTS + ".list.subject", hasItem(SUBJECT)); + String newMessageId = listMessageIdsForAccount(homerCredential).get(0); + + assertThat(getMessageContent(homerCredential, newMessageId) + .getString("methodResponses[0][1].list[0].subject")).isEqualTo(SUBJECT); } @Tag(BasicFeature.TAG) @Test void vaultExportShouldExportZipContainsVaultMessagesToShareeWhenJmapDeleteMessage() throws Exception { bartSendMessageToHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); - String messageIdOfHomer = listMessageIdsForAccount(homerAccessToken).get(0); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); + String messageIdOfHomer = listMessageIdsForAccount(homerCredential).get(0); - homerDeletesMessages(listMessageIdsForAccount(homerAccessToken)); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(0)); + homerDeletesMessages(listMessageIdsForAccount(homerCredential)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0)); - String fileLocation = exportAndGetFileLocationFromLastMail(EXPORT_ALL_HOMER_MESSAGES_TO_BART, bartAccessToken); + String fileLocation = exportAndGetFileLocationFromLastMail(EXPORT_ALL_HOMER_MESSAGES_TO_BART, bartCredential); try (ZipAssert zipAssert = assertThatZip(new FileInputStream(fileLocation))) { zipAssert.hasEntriesSize(1) @@ -531,8 +501,8 @@ public abstract class DeletedMessageVaultIntegrationTest { @Test void vaultExportShouldExportZipContainsVaultMessagesToShareeWhenImapDeleteMessage(GuiceJamesServer jmapServer) throws Exception { bartSendMessageToHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); - String messageIdOfHomer = listMessageIdsForAccount(homerAccessToken).get(0); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); + String messageIdOfHomer = listMessageIdsForAccount(homerCredential).get(0); testIMAPClient.connect(LOCALHOST_IP, jmapServer.getProbe(ImapGuiceProbe.class).getImapPort()) .login(HOMER, PASSWORD) @@ -540,9 +510,9 @@ public abstract class DeletedMessageVaultIntegrationTest { .setFlagsForAllMessagesInMailbox("\\Deleted"); testIMAPClient.expunge(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(0)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0)); - String fileLocation = exportAndGetFileLocationFromLastMail(EXPORT_ALL_HOMER_MESSAGES_TO_BART, bartAccessToken); + String fileLocation = exportAndGetFileLocationFromLastMail(EXPORT_ALL_HOMER_MESSAGES_TO_BART, bartCredential); try (ZipAssert zipAssert = assertThatZip(new FileInputStream(fileLocation))) { zipAssert.hasEntriesSize(1) @@ -554,8 +524,8 @@ public abstract class DeletedMessageVaultIntegrationTest { @Test public void vaultExportShouldExportZipContainsVaultMessagesToShareeWhenImapDeletedMailbox(GuiceJamesServer jmapServer) throws Exception { bartSendMessageToHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); - String messageIdOfHomer = listMessageIdsForAccount(homerAccessToken).get(0); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); + String messageIdOfHomer = listMessageIdsForAccount(homerCredential).get(0); testIMAPClient.connect(LOCALHOST_IP, jmapServer.getProbe(ImapGuiceProbe.class).getImapPort()) .login(HOMER, PASSWORD) @@ -565,9 +535,9 @@ public abstract class DeletedMessageVaultIntegrationTest { testIMAPClient.delete(MAILBOX_NAME); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(0)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0)); - String fileLocation = exportAndGetFileLocationFromLastMail(EXPORT_ALL_HOMER_MESSAGES_TO_BART, bartAccessToken); + String fileLocation = exportAndGetFileLocationFromLastMail(EXPORT_ALL_HOMER_MESSAGES_TO_BART, bartCredential); try (ZipAssert zipAssert = assertThatZip(new FileInputStream(fileLocation))) { zipAssert.hasEntriesSize(1) @@ -578,24 +548,26 @@ public abstract class DeletedMessageVaultIntegrationTest { @Test void vaultExportShouldExportZipContainsOnlyMatchedMessages() throws Exception { bartSendMessageToHomerWithSubject(FIRST_SUBJECT); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); - String firstMessageIdOfHomer = listMessageIdsForAccount(homerAccessToken).get(0); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); + String firstMessageIdOfHomer = listMessageIdsForAccount(homerCredential).get(0); bartSendMessageToHomerWithSubject(SECOND_SUBJECT); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(2)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(2)); - homerDeletesMessages(listMessageIdsForAccount(homerAccessToken)); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(0)); + homerDeletesMessages(listMessageIdsForAccount(homerCredential)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0)); ExportRequest exportRequest = ExportRequest .userExportFrom(HOMER) .exportTo(BART) - .query("{" + - " \"fieldName\": \"subject\"," + - " \"operator\": \"equals\"," + - " \"value\": \"" + FIRST_SUBJECT + "\"" + - "}"); - String fileLocation = exportAndGetFileLocationFromLastMail(exportRequest, bartAccessToken); + .query(""" + { + "fieldName": "subject", + "operator": "equals", + "value": "%s" + } + """.formatted(FIRST_SUBJECT)); + String fileLocation = exportAndGetFileLocationFromLastMail(exportRequest, bartCredential); try (ZipAssert zipAssert = assertThatZip(new FileInputStream(fileLocation))) { zipAssert.containsOnlyEntriesMatching(hasName(firstMessageIdOfHomer + ".eml")); @@ -606,10 +578,10 @@ public abstract class DeletedMessageVaultIntegrationTest { void vaultExportShouldExportEmptyZipWhenQueryDoesntMatch() throws Exception { bartSendMessageToHomerWithSubject(FIRST_SUBJECT); bartSendMessageToHomerWithSubject(SECOND_SUBJECT); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(2)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(2)); - homerDeletesMessages(listMessageIdsForAccount(homerAccessToken)); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(0)); + homerDeletesMessages(listMessageIdsForAccount(homerCredential)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0)); ExportRequest exportRequest = ExportRequest .userExportFrom(HOMER) @@ -619,7 +591,7 @@ public abstract class DeletedMessageVaultIntegrationTest { " \"operator\": \"equals\"," + " \"value\": \"non matching\"" + "}"); - String fileLocation = exportAndGetFileLocationFromLastMail(exportRequest, bartAccessToken); + String fileLocation = exportAndGetFileLocationFromLastMail(exportRequest, bartCredential); try (ZipAssert zipAssert = assertThatZip(new FileInputStream(fileLocation))) { zipAssert.hasNoEntry(); @@ -628,7 +600,7 @@ public abstract class DeletedMessageVaultIntegrationTest { @Test void vaultExportShouldExportEmptyZipWhenVaultIsEmpty() throws Exception { - String fileLocation = exportAndGetFileLocationFromLastMail(EXPORT_ALL_HOMER_MESSAGES_TO_BART, bartAccessToken); + String fileLocation = exportAndGetFileLocationFromLastMail(EXPORT_ALL_HOMER_MESSAGES_TO_BART, bartCredential); try (ZipAssert zipAssert = assertThatZip(new FileInputStream(fileLocation))) { zipAssert.hasNoEntry(); @@ -638,13 +610,13 @@ public abstract class DeletedMessageVaultIntegrationTest { @Test void vaultExportShouldResponseIdempotentSideEffect() throws Exception { bartSendMessageToHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); - homerDeletesMessages(listMessageIdsForAccount(homerAccessToken)); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(0)); + homerDeletesMessages(listMessageIdsForAccount(homerCredential)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0)); - String fileLocationFirstExport = exportAndGetFileLocationFromLastMail(EXPORT_ALL_HOMER_MESSAGES_TO_BART, bartAccessToken); - String fileLocationSecondExport = exportAndGetFileLocationFromLastMail(EXPORT_ALL_HOMER_MESSAGES_TO_BART, bartAccessToken); + String fileLocationFirstExport = exportAndGetFileLocationFromLastMail(EXPORT_ALL_HOMER_MESSAGES_TO_BART, bartCredential); + String fileLocationSecondExport = exportAndGetFileLocationFromLastMail(EXPORT_ALL_HOMER_MESSAGES_TO_BART, bartCredential); try (ZipAssert zipAssert = assertThatZip(new FileInputStream(fileLocationFirstExport))) { zipAssert.hasSameContentWith(new FileInputStream(fileLocationSecondExport)); @@ -656,15 +628,15 @@ public abstract class DeletedMessageVaultIntegrationTest { bartSendMessageToHomer(); bartSendMessageToHomer(); bartSendMessageToHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(3)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(3)); - homerDeletesMessages(listMessageIdsForAccount(homerAccessToken)); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(0)); + homerDeletesMessages(listMessageIdsForAccount(homerCredential)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0)); clock.setInstant(TWO_MONTH_AFTER_ONE_YEAR_EXPIRATION.toInstant()); purgeVault(webAdminApi); - String fileLocation = exportAndGetFileLocationFromLastMail(EXPORT_ALL_HOMER_MESSAGES_TO_BART, bartAccessToken); + String fileLocation = exportAndGetFileLocationFromLastMail(EXPORT_ALL_HOMER_MESSAGES_TO_BART, bartCredential); try (ZipAssert zipAssert = assertThatZip(new FileInputStream(fileLocation))) { zipAssert.hasNoEntry(); } @@ -673,23 +645,23 @@ public abstract class DeletedMessageVaultIntegrationTest { @Test void vaultPurgeShouldMakeExportProduceAZipWhenOneMessageIsNotExpired(UpdatableTickingClock clock) throws Exception { bartSendMessageToHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); - homerDeletesMessages(listMessageIdsForAccount(homerAccessToken)); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(0)); + homerDeletesMessages(listMessageIdsForAccount(homerCredential)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0)); bartSendMessageToHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); - String messageIdOfNotExpiredMessage = listMessageIdsForAccount(homerAccessToken).get(0); + String messageIdOfNotExpiredMessage = listMessageIdsForAccount(homerCredential).get(0); clock.setInstant(TWO_MONTH_AFTER_ONE_YEAR_EXPIRATION.toInstant()); - homerDeletesMessages(listMessageIdsForAccount(homerAccessToken)); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(0)); + homerDeletesMessages(listMessageIdsForAccount(homerCredential)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0)); purgeVault(webAdminApi); - String fileLocation = exportAndGetFileLocationFromLastMail(EXPORT_ALL_HOMER_MESSAGES_TO_BART, bartAccessToken); + String fileLocation = exportAndGetFileLocationFromLastMail(EXPORT_ALL_HOMER_MESSAGES_TO_BART, bartCredential); try (ZipAssert zipAssert = assertThatZip(new FileInputStream(fileLocation))) { zipAssert.hasEntriesSize(1) .allSatisfies(entry -> hasName(messageIdOfNotExpiredMessage + ".eml")); @@ -701,14 +673,14 @@ public abstract class DeletedMessageVaultIntegrationTest { bartSendMessageToHomer(); bartSendMessageToHomer(); bartSendMessageToHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(3)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(3)); - homerDeletesMessages(listMessageIdsForAccount(homerAccessToken)); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(0)); + homerDeletesMessages(listMessageIdsForAccount(homerCredential)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0)); purgeVault(webAdminApi); - String fileLocation = exportAndGetFileLocationFromLastMail(EXPORT_ALL_HOMER_MESSAGES_TO_BART, bartAccessToken); + String fileLocation = exportAndGetFileLocationFromLastMail(EXPORT_ALL_HOMER_MESSAGES_TO_BART, bartCredential); try (ZipAssert zipAssert = assertThatZip(new FileInputStream(fileLocation))) { zipAssert.hasEntriesSize(3); } @@ -717,31 +689,31 @@ public abstract class DeletedMessageVaultIntegrationTest { @Test void vaultPurgeShouldNotAppendMessageToTheUserMailbox(UpdatableTickingClock clock) { bartSendMessageToHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); - homerDeletesMessages(listMessageIdsForAccount(homerAccessToken)); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(0)); + homerDeletesMessages(listMessageIdsForAccount(homerCredential)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0)); clock.setInstant(TWO_MONTH_AFTER_ONE_YEAR_EXPIRATION.toInstant()); purgeVault(webAdminApi); - assertThat(listMessageIdsForAccount(homerAccessToken)) + assertThat(listMessageIdsForAccount(homerCredential)) .hasSize(0); } @Test void vaultDeleteShouldDeleteMessageThenExportWithNoEntry() throws Exception { bartSendMessageToHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); - String messageIdOfHomer = listMessageIdsForAccount(homerAccessToken).get(0); + String messageIdOfHomer = listMessageIdsForAccount(homerCredential).get(0); - homerDeletesMessages(listMessageIdsForAccount(homerAccessToken)); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(0)); + homerDeletesMessages(listMessageIdsForAccount(homerCredential)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0)); deleteFromVault(webAdminApi, HOMER, messageIdOfHomer); - String fileLocation = exportAndGetFileLocationFromLastMail(EXPORT_ALL_HOMER_MESSAGES_TO_BART, bartAccessToken); + String fileLocation = exportAndGetFileLocationFromLastMail(EXPORT_ALL_HOMER_MESSAGES_TO_BART, bartCredential); try (ZipAssert zipAssert = assertThatZip(new FileInputStream(fileLocation))) { zipAssert.hasNoEntry(); } @@ -750,13 +722,13 @@ public abstract class DeletedMessageVaultIntegrationTest { @Test void vaultDeleteShouldNotDeleteEmptyVaultThenExportNoEntry() throws Exception { bartSendMessageToHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); - String messageIdOfHomer = listMessageIdsForAccount(homerAccessToken).get(0); + String messageIdOfHomer = listMessageIdsForAccount(homerCredential).get(0); deleteFromVault(webAdminApi, HOMER, messageIdOfHomer); - String fileLocation = exportAndGetFileLocationFromLastMail(EXPORT_ALL_HOMER_MESSAGES_TO_BART, bartAccessToken); + String fileLocation = exportAndGetFileLocationFromLastMail(EXPORT_ALL_HOMER_MESSAGES_TO_BART, bartCredential); try (ZipAssert zipAssert = assertThatZip(new FileInputStream(fileLocation))) { zipAssert.hasNoEntry(); } @@ -765,18 +737,18 @@ public abstract class DeletedMessageVaultIntegrationTest { @Test void vaultDeleteShouldNotDeleteNotMatchedMessageInVaultThenExportAnEntry() throws Exception { bartSendMessageToHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); - String messageIdOfHomer = listMessageIdsForAccount(homerAccessToken).get(0); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); + String messageIdOfHomer = listMessageIdsForAccount(homerCredential).get(0); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(bartAccessToken)).hasSize(1)); - String messageIdOfBart = listMessageIdsForAccount(bartAccessToken).get(0); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(bartCredential)).hasSize(1)); + String messageIdOfBart = listMessageIdsForAccount(bartCredential).get(0); - homerDeletesMessages(listMessageIdsForAccount(homerAccessToken)); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(0)); + homerDeletesMessages(listMessageIdsForAccount(homerCredential)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0)); deleteFromVault(webAdminApi, HOMER, messageIdOfBart); - String fileLocation = exportAndGetFileLocationFromLastMail(EXPORT_ALL_HOMER_MESSAGES_TO_BART, bartAccessToken); + String fileLocation = exportAndGetFileLocationFromLastMail(EXPORT_ALL_HOMER_MESSAGES_TO_BART, bartCredential); try (ZipAssert zipAssert = assertThatZip(new FileInputStream(fileLocation))) { zipAssert.hasEntriesSize(1) .allSatisfies(entry -> hasName(messageIdOfHomer + ".eml")); @@ -786,37 +758,37 @@ public abstract class DeletedMessageVaultIntegrationTest { @Test void vaultDeleteShouldNotAppendMessageToTheUserMailbox() { bartSendMessageToHomer(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); - String messageIdOfHomer = listMessageIdsForAccount(homerAccessToken).get(0); + String messageIdOfHomer = listMessageIdsForAccount(homerCredential).get(0); - homerDeletesMessages(listMessageIdsForAccount(homerAccessToken)); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(0)); + homerDeletesMessages(listMessageIdsForAccount(homerCredential)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0)); deleteFromVault(webAdminApi, HOMER, messageIdOfHomer); - assertThat(listMessageIdsForAccount(homerAccessToken)) + assertThat(listMessageIdsForAccount(homerCredential)) .hasSize(0); } @Test void vaultDeleteShouldDeleteAllMessagesHavingSameBlobContent() throws Exception { bartSendMessageToHomerAndJack(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); - String homerInboxMessageId = listMessageIdsForAccount(homerAccessToken).get(0); + String homerInboxMessageId = listMessageIdsForAccount(homerCredential).get(0); homerDeletesMessages(ImmutableList.of(homerInboxMessageId)); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(0)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0)); // the message same with homer's one in inbox - String jackInboxMessageId = listMessageIdsForAccount(jackAccessToken).get(0); + String jackInboxMessageId = listMessageIdsForAccount(jackCredential).get(0); jackDeletesMessages(ImmutableList.of(jackInboxMessageId)); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(jackAccessToken)).hasSize(0)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(jackCredential)).hasSize(0)); // delete from homer's vault, expecting the message contains the same blob in jack's vault will be deleted deleteFromVault(webAdminApi, HOMER, homerInboxMessageId); - String fileLocationOfBartMessages = exportAndGetFileLocationFromLastMail(EXPORT_ALL_JACK_MESSAGES_TO_HOMER, homerAccessToken); + String fileLocationOfBartMessages = exportAndGetFileLocationFromLastMail(EXPORT_ALL_JACK_MESSAGES_TO_HOMER, homerCredential); try (ZipAssert zipAssert = assertThatZip(new FileInputStream(fileLocationOfBartMessages))) { zipAssert.hasNoEntry(); } @@ -825,64 +797,94 @@ public abstract class DeletedMessageVaultIntegrationTest { @Test void vaultDeleteShouldNotDeleteAllMessagesHavingSameBlobContentWhenMessageNotDeletedWithinTheSameMonth(UpdatableTickingClock clock) throws Exception { bartSendMessageToHomerAndJack(); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1)); - String homerInboxMessageId = listMessageIdsForAccount(homerAccessToken).get(0); + String homerInboxMessageId = listMessageIdsForAccount(homerCredential).get(0); homerDeletesMessages(ImmutableList.of(homerInboxMessageId)); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(0)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0)); // one year later, delete jack's message clock.setInstant(NOW.plusYears(1).toInstant()); // the message same with homer's one in inbox - String jackInboxMessageId = listMessageIdsForAccount(jackAccessToken).get(0); + String jackInboxMessageId = listMessageIdsForAccount(jackCredential).get(0); jackDeletesMessages(ImmutableList.of(jackInboxMessageId)); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(jackAccessToken)).hasSize(0)); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(jackCredential)).hasSize(0)); // delete from homer's vault, expecting jack's vault still be intact deleteFromVault(webAdminApi, HOMER, homerInboxMessageId); - String fileLocationOfBartMessages = exportAndGetFileLocationFromLastMail(EXPORT_ALL_JACK_MESSAGES_TO_HOMER, homerAccessToken); + String fileLocationOfBartMessages = exportAndGetFileLocationFromLastMail(EXPORT_ALL_JACK_MESSAGES_TO_HOMER, homerCredential); try (ZipAssert zipAssert = assertThatZip(new FileInputStream(fileLocationOfBartMessages))) { zipAssert.hasEntriesSize(1) .allSatisfies(entry -> hasName(jackInboxMessageId + ".eml")); } } - private String exportAndGetFileLocationFromLastMail(ExportRequest exportRequest, AccessToken shareeAccessToken) { - int currentNumberOfMessages = listMessageIdsForAccount(shareeAccessToken).size(); + private String exportAndGetFileLocationFromLastMail(ExportRequest exportRequest, UserCredential shareeCredential) { + int currentNumberOfMessages = listMessageIdsForAccount(shareeCredential).size(); exportVaultContent(webAdminApi, exportRequest); - WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(shareeAccessToken)).hasSize(currentNumberOfMessages + 1)); - String exportingMessageId = getLatestMessageId(shareeAccessToken, Role.INBOX); + WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(shareeCredential)).hasSize(currentNumberOfMessages + 1)); + String exportingMessageId = getLatestMessageId(shareeCredential, Role.INBOX); - return exportedFileLocationFromMailHeader(exportingMessageId, shareeAccessToken); + return exportedFileLocationFromMailHeader(exportingMessageId, shareeCredential); } - private String exportedFileLocationFromMailHeader(String messageId, AccessToken accessToken) { - return with() - .header("Authorization", accessToken.asString()) - .body("[[\"getMessages\", {\"ids\": [\"" + messageId + "\"]}, \"#0\"]]") - .post("/jmap") - .jsonPath() - .getList(ARGUMENTS + ".list.headers.corresponding-file", String.class) - .get(0); + private String exportedFileLocationFromMailHeader(String messageId, UserCredential userCredential) { + List<Map<String, String>> headers = with() + .auth().basic(userCredential.username().asString(), userCredential.password()) + .body(""" + { + "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail", "urn:ietf:params:jmap:submission"], + "methodCalls": [ + ["Email/get", { + "accountId": "%s", + "ids": ["%s"], + "properties":["bodyStructure"], + "bodyProperties":["name", "type","headers"] + }, "c2"] + ] + } + """.formatted(userCredential.accountId(), messageId)) + .post("/jmap") + .then() + .statusCode(200) + .contentType(JSON) + .extract() + .body() + .path("methodResponses[0][1].list[0].bodyStructure.headers"); + + return headers.stream() + .filter(header -> header.get("name").equals("corresponding-file")) + .findFirst() + .orElseThrow(() -> new RuntimeException("No corresponding-file header found")) + .get("value").trim(); } private void homerSharesHisMailboxWithBart() { with() - .header("Authorization", homerAccessToken.asString()) - .body("[" + - " [ \"setMailboxes\"," + - " {" + - " \"update\": {" + - " \"" + otherMailboxId.serialize() + "\" : {" + - " \"sharedWith\" : {\"" + BART + "\": [\"l\", \"w\", \"t\", \"r\"]}" + - " }" + - " }" + - " }," + - " \"#0\"" + - " ]" + - "]") + .auth().basic(homerCredential.username().asString(), homerCredential.password()) + .body(""" + { + "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail", "urn:apache:james:params:jmap:mail:shares" ], + "methodCalls": [ + [ + "Mailbox/set", + { + "accountId": "%s", + "update": { + "%s": { + "sharedWith": { + "%s":["r", "l", "w", "t"] + } + } + } + }, + "c1" + ] + ] + } + """.formatted(homerCredential.accountId(), otherMailboxId.serialize(), BART)) .post("/jmap"); } @@ -891,71 +893,130 @@ public abstract class DeletedMessageVaultIntegrationTest { } private void bartSendMessageToHomerAndJack() { - String messageCreationId = "creationId"; - String outboxId = getOutboxId(bartAccessToken); - String bigEnoughBody = Strings.repeat("123456789\n", 12 * 100); - String requestBody = "[" + - " [" + - " \"setMessages\"," + - " {" + - " \"create\": { \"" + messageCreationId + "\" : {" + - " \"headers\":{\"Disposition-Notification-To\":\"" + BART + "\"}," + - " \"from\": { \"name\": \"Bob\", \"email\": \"" + BART + "\"}," + - " \"to\": [{ \"name\": \"Homer\", \"email\": \"" + HOMER + "\"}, { \"name\": \"Jack\", \"email\": \"" + JACK + "\"}]," + - " \"subject\": \"" + SUBJECT + "\"," + - " \"textBody\": \"" + bigEnoughBody + "\"," + - " \"htmlBody\": \"Test <b>body</b>, HTML version\"," + - " \"mailboxIds\": [\"" + outboxId + "\"] " + - " }}" + - " }," + - " \"#0\"" + - " ]" + - "]"; + String outboxId = getOutboxId(bartCredential); + + String requestBody = + "{" + + " \"using\": [\"urn:ietf:params:jmap:core\", \"urn:ietf:params:jmap:mail\", \"urn:ietf:params:jmap:submission\"]," + + " \"methodCalls\": [" + + " [\"Email/set\", {" + + " \"accountId\": \"" + bartCredential.accountId() + "\"," + + " \"create\": {" + + " \"e1526\": {" + + " \"mailboxIds\": { \"" + outboxId + "\": true }," + + " \"subject\": \"" + SUBJECT + "\"," + + " \"htmlBody\": [{" + + " \"partId\": \"a49d\"," + + " \"type\": \"text/html\"" + + " }]," + + " \"bodyValues\": {" + + " \"a49d\": {" + + " \"value\": \"Test <b>body</b>, HTML version\"" + + " }" + + " }," + + " \"to\": [{" + + " \"email\": \"" + HOMER + "\"" + + " }, {" + + " \"email\": \"" + JACK + "\"" + + " }]," + + " \"from\": [{" + + " \"email\": \"" + BART + "\"" + + " }]" + + " }" + + " }" + + " }, \"c1\"]," + + " [\"Email/get\", {" + + " \"accountId\": \"" + bartCredential.accountId() + "\"," + + " \"ids\": [\"#e1526\"]," + + " \"properties\": [\"sentAt\"]" + + " }, \"c2\"]," + + " [\"EmailSubmission/set\", {" + + " \"accountId\": \"" + bartCredential.accountId() + "\"," + + " \"create\": {" + + " \"k1490\": {" + + " \"emailId\": \"#e1526\"," + + " \"envelope\": {" + + " \"mailFrom\": {\"email\": \"" + BART + "\"}," + + " \"rcptTo\": [{" + + " \"email\": \"" + HOMER + "\"" + + " }, {" + + " \"email\": \"" + JACK + "\"" + + " }]" + + " }" + + " }" + + " }" + + " }, \"c3\"]" + + " ]" + + "}"; with() - .header("Authorization", bartAccessToken.asString()) + .auth().basic(bartCredential.username().asString(), bartCredential.password()) .body(requestBody) .post("/jmap") .then() - .extract() - .body() - .path(ARGUMENTS + ".created." + messageCreationId + ".id"); + .statusCode(200) + .contentType(JSON) + .body("methodResponses[2][1].created", Matchers.is(notNullValue())); } private void bartSendMessageToHomerWithSubject(String subject) { - String messageCreationId = "creationId"; - String outboxId = getOutboxId(bartAccessToken); - String bigEnoughBody = Strings.repeat("123456789\n", 12 * 100); - String requestBody = "[" + - " [" + - " \"setMessages\"," + - " {" + - " \"create\": { \"" + messageCreationId + "\" : {" + - " \"headers\":{\"Disposition-Notification-To\":\"" + BART + "\"}," + - " \"from\": { \"name\": \"Bob\", \"email\": \"" + BART + "\"}," + - " \"to\": [{ \"name\": \"User\", \"email\": \"" + HOMER + "\"}]," + - " \"subject\": \"" + subject + "\"," + - " \"textBody\": \"" + bigEnoughBody + "\"," + - " \"htmlBody\": \"Test <b>body</b>, HTML version\"," + - " \"mailboxIds\": [\"" + outboxId + "\"] " + - " }}" + - " }," + - " \"#0\"" + - " ]" + - "]"; + String outboxId = getOutboxId(bartCredential); + String requestBody = + "{" + + " \"using\": [\"urn:ietf:params:jmap:core\", \"urn:ietf:params:jmap:mail\", \"urn:ietf:params:jmap:submission\"]," + + " \"methodCalls\": [" + + " [\"Email/set\", {" + + " \"accountId\": \"" + bartCredential.accountId() + "\"," + + " \"create\": {" + + " \"e1526\": {" + + " \"mailboxIds\": { \"" + outboxId + "\": true }," + + " \"subject\": \"" + subject + "\"," + + " \"htmlBody\": [{" + + " \"partId\": \"a49d\"," + + " \"type\": \"text/html\"" + + " }]," + + " \"bodyValues\": {" + + " \"a49d\": {" + + " \"value\": \"Test <b>body</b>, HTML version\"" + + " }" + + " }," + + " \"to\": [{\"email\": \"" + HOMER + "\"}]," + + " \"from\": [{\"email\": \"" + BART + "\"}]" + + " }" + + " }" + + " }, \"c1\"]," + + " [\"Email/get\", {" + + " \"accountId\": \"" + bartCredential.accountId() + "\"," + + " \"ids\": [\"#e1526\"]," + + " \"properties\": [\"sentAt\"]" + + " }, \"c2\"]," + + " [\"EmailSubmission/set\", {" + + " \"accountId\": \"" + bartCredential.accountId() + "\"," + + " \"create\": {" + + " \"k1490\": {" + + " \"emailId\": \"#e1526\"," + + " \"envelope\": {" + + " \"mailFrom\": {\"email\": \"" + BART + "\"}," + + " \"rcptTo\": [{\"email\": \"" + HOMER + "\"}]" + + " }" + + " }" + + " }" + + " }, \"c3\"]" + + " ]" + + "}"; with() - .header("Authorization", bartAccessToken.asString()) + .auth().basic(bartCredential.username().asString(), bartCredential.password()) .body(requestBody) .post("/jmap") .then() - .extract() - .body() - .path(ARGUMENTS + ".created." + messageCreationId + ".id"); + .statusCode(200) + .contentType(JSON) + .body("methodResponses[2][1].created", Matchers.is(notNullValue())); } private void homerDeletesMessages(List<String> idsToDestroy) { - deleteMessages(homerAccessToken, idsToDestroy); + deleteMessages(homerCredential, idsToDestroy); // Grace period for the vault try { Thread.sleep(1000); @@ -965,11 +1026,11 @@ public abstract class DeletedMessageVaultIntegrationTest { } private void bartDeletesMessages(List<String> idsToDestroy) { - deleteMessages(bartAccessToken, idsToDestroy); + deleteMessages(bartCredential, idsToDestroy); } private void jackDeletesMessages(List<String> idsToDestroy) { - deleteMessages(jackAccessToken, idsToDestroy); + deleteMessages(jackCredential, idsToDestroy); } private void restoreAllMessagesOfHomer() { @@ -981,27 +1042,31 @@ public abstract class DeletedMessageVaultIntegrationTest { } private void homerMovesTheMailInAnotherMailbox(String messageId) { - String updateRequestBody = "[" + - " [" + - " \"setMessages\"," + - " {" + - " \"update\": { \"" + messageId + "\" : {" + - " \"mailboxIds\": [\"" + otherMailboxId.serialize() + "\"]" + - " }}" + - " }," + - " \"#0\"" + - " ]" + - "]"; - given() - .header("Authorization", homerAccessToken.asString()) - .body(updateRequestBody) + .auth().basic(homerCredential.username().asString(), homerCredential.password()) + .body(""" + { + "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"], + "methodCalls": [ + ["Email/set", { + "accountId": "%s", + "update": { + "%s":{ + "mailboxIds": { "%s" : true} + } + } + }, "c1"]] + }""".formatted(homerCredential.accountId(), messageId, otherMailboxId.serialize())) .when() - .post("/jmap"); + .post("/jmap") + .then() + .statusCode(200) + .contentType(JSON); } + private boolean homerHasMailboxWithRole(Role role) { - return getAllMailboxesIds(homerAccessToken).stream() + return getAllMailboxesIds(homerCredential).stream() .filter(mailbox -> mailbox.get("role") != null) .anyMatch(mailbox -> mailbox.get("role").equals(role.serialize()) && mailbox.get("name").equals(role.getDefaultMailbox())); diff --git a/server/protocols/webadmin-integration-test/webadmin-integration-test-common/src/main/java/org/apache/james/webadmin/integration/vault/LinshareBlobExportMechanismIntegrationTest.java b/server/protocols/webadmin-integration-test/webadmin-integration-test-common/src/main/java/org/apache/james/webadmin/integration/vault/LinshareBlobExportMechanismIntegrationTest.java index 251967440a..d733ec5432 100644 --- a/server/protocols/webadmin-integration-test/webadmin-integration-test-common/src/main/java/org/apache/james/webadmin/integration/vault/LinshareBlobExportMechanismIntegrationTest.java +++ b/server/protocols/webadmin-integration-test/webadmin-integration-test-common/src/main/java/org/apache/james/webadmin/integration/vault/LinshareBlobExportMechanismIntegrationTest.java @@ -20,16 +20,15 @@ package org.apache.james.webadmin.integration.vault; import static io.restassured.RestAssured.with; -import static org.apache.james.jmap.HttpJmapAuthentication.authenticateJamesUser; -import static org.apache.james.jmap.JMAPTestingConstants.ARGUMENTS; +import static io.restassured.http.ContentType.JSON; import static org.apache.james.jmap.JMAPTestingConstants.DOMAIN; import static org.apache.james.jmap.JMAPTestingConstants.LOCALHOST_IP; import static org.apache.james.jmap.JMAPTestingConstants.calmlyAwait; import static org.apache.james.jmap.JMAPTestingConstants.jmapRequestSpecBuilder; -import static org.apache.james.jmap.JmapCommonRequests.deleteMessages; -import static org.apache.james.jmap.JmapCommonRequests.getOutboxId; -import static org.apache.james.jmap.JmapCommonRequests.listMessageIdsForAccount; -import static org.apache.james.jmap.LocalHostURIBuilder.baseUri; +import static org.apache.james.jmap.JmapRFCCommonRequests.UserCredential; +import static org.apache.james.jmap.JmapRFCCommonRequests.deleteMessages; +import static org.apache.james.jmap.JmapRFCCommonRequests.getUserCredential; +import static org.apache.james.jmap.JmapRFCCommonRequests.listMessageIdsForAccount; import static org.apache.james.linshare.LinshareExtension.LinshareAPIForTechnicalAccountTesting; import static org.apache.james.linshare.LinshareExtension.LinshareAPIForUserTesting; import static org.apache.james.linshare.LinshareFixture.MATCH_ALL_QUERY; @@ -38,13 +37,14 @@ import static org.apache.james.mailbox.backup.ZipAssert.assertThatZip; import static org.apache.james.webadmin.integration.vault.DeletedMessagesVaultRequests.exportVaultContent; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Durations.TEN_SECONDS; +import static org.hamcrest.Matchers.notNullValue; import java.io.ByteArrayInputStream; import java.util.List; import org.apache.james.GuiceJamesServer; import org.apache.james.core.Username; -import org.apache.james.jmap.AccessToken; +import org.apache.james.jmap.JmapRFCCommonRequests; import org.apache.james.jmap.draft.JmapGuiceProbe; import org.apache.james.linshare.client.Document; import org.apache.james.mailbox.DefaultMailboxes; @@ -59,6 +59,7 @@ import org.apache.james.utils.TestIMAPClient; import org.apache.james.utils.WebAdminGuiceProbe; import org.apache.james.webadmin.WebAdminUtils; import org.awaitility.core.ConditionFactory; +import org.hamcrest.Matchers; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -83,8 +84,9 @@ public abstract class LinshareBlobExportMechanismIntegrationTest { @RegisterExtension TestIMAPClient testIMAPClient = new TestIMAPClient(); - private AccessToken homerAccessToken; - private AccessToken bartAccessToken; + private UserCredential homerCredential; + private UserCredential bartCredential; + private GuiceJamesServer jmapServer; private RequestSpecification webAdminApi; private LinshareAPIForUserTesting user1LinshareAPI; @@ -110,8 +112,8 @@ public abstract class LinshareBlobExportMechanismIntegrationTest { MailboxProbe mailboxProbe = jmapServer.getProbe(MailboxProbeImpl.class); mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, HOMER, DefaultMailboxes.INBOX); - homerAccessToken = authenticateJamesUser(baseUri(jmapPort), Username.of(HOMER), HOMER_PASSWORD); - bartAccessToken = authenticateJamesUser(baseUri(jmapPort), Username.of(BART), BART_PASSWORD); + homerCredential = getUserCredential(Username.of(HOMER), HOMER_PASSWORD); + bartCredential = getUserCredential(Username.of(BART), BART_PASSWORD); user1LinshareAPI = LinshareAPIForUserTesting.from(USER_1); } @@ -120,10 +122,10 @@ public abstract class LinshareBlobExportMechanismIntegrationTest { void exportShouldShareTheDocumentViaLinshareWhenJmapDelete() { bartSendMessageToHomer(); - WAIT_TEN_SECONDS.until(() -> listMessageIdsForAccount(homerAccessToken).size() == 1); + WAIT_TEN_SECONDS.until(() -> listMessageIdsForAccount(homerCredential).size() == 1); - homerDeletesMessages(listMessageIdsForAccount(homerAccessToken)); - WAIT_TEN_SECONDS.until(() -> listMessageIdsForAccount(homerAccessToken).isEmpty()); + homerDeletesMessages(listMessageIdsForAccount(homerCredential)); + WAIT_TEN_SECONDS.until(() -> listMessageIdsForAccount(homerCredential).isEmpty()); exportVaultContent(webAdminApi, EXPORT_ALL_HOMER_MESSAGES_TO_USER_1); @@ -136,7 +138,7 @@ public abstract class LinshareBlobExportMechanismIntegrationTest { void exportShouldShareTheDocumentViaLinshareWhenImapDelete() throws Exception { bartSendMessageToHomer(); - WAIT_TEN_SECONDS.until(() -> listMessageIdsForAccount(homerAccessToken).size() == 1); + WAIT_TEN_SECONDS.until(() -> listMessageIdsForAccount(homerCredential).size() == 1); testIMAPClient.connect(LOCALHOST_IP, jmapServer.getProbe(ImapGuiceProbe.class).getImapPort()) .login(HOMER, HOMER_PASSWORD) @@ -144,7 +146,7 @@ public abstract class LinshareBlobExportMechanismIntegrationTest { .setFlagsForAllMessagesInMailbox("\\Deleted"); testIMAPClient.expunge(); - WAIT_TEN_SECONDS.until(() -> listMessageIdsForAccount(homerAccessToken).isEmpty()); + WAIT_TEN_SECONDS.until(() -> listMessageIdsForAccount(homerCredential).isEmpty()); exportVaultContent(webAdminApi, EXPORT_ALL_HOMER_MESSAGES_TO_USER_1); @@ -157,10 +159,10 @@ public abstract class LinshareBlobExportMechanismIntegrationTest { void exportShouldShareNonEmptyZipViaLinshareWhenJmapDelete(LinshareAPIForTechnicalAccountTesting linshareAPIForTechnicalAccountTesting) throws Exception { bartSendMessageToHomer(); - WAIT_TEN_SECONDS.until(() -> listMessageIdsForAccount(homerAccessToken).size() == 1); + WAIT_TEN_SECONDS.until(() -> listMessageIdsForAccount(homerCredential).size() == 1); - homerDeletesMessages(listMessageIdsForAccount(homerAccessToken)); - WAIT_TEN_SECONDS.until(() -> listMessageIdsForAccount(homerAccessToken).isEmpty()); + homerDeletesMessages(listMessageIdsForAccount(homerCredential)); + WAIT_TEN_SECONDS.until(() -> listMessageIdsForAccount(homerCredential).isEmpty()); exportVaultContent(webAdminApi, EXPORT_ALL_HOMER_MESSAGES_TO_USER_1); @@ -177,7 +179,7 @@ public abstract class LinshareBlobExportMechanismIntegrationTest { void exportShouldShareNonEmptyZipViaLinshareWhenImapDelete(LinshareAPIForTechnicalAccountTesting linshareAPIForTechnicalAccountTesting) throws Exception { bartSendMessageToHomer(); - WAIT_TEN_SECONDS.until(() -> listMessageIdsForAccount(homerAccessToken).size() == 1); + WAIT_TEN_SECONDS.until(() -> listMessageIdsForAccount(homerCredential).size() == 1); testIMAPClient.connect(LOCALHOST_IP, jmapServer.getProbe(ImapGuiceProbe.class).getImapPort()) .login(HOMER, HOMER_PASSWORD) @@ -185,7 +187,7 @@ public abstract class LinshareBlobExportMechanismIntegrationTest { .setFlagsForAllMessagesInMailbox("\\Deleted"); testIMAPClient.expunge(); - WAIT_TEN_SECONDS.until(() -> listMessageIdsForAccount(homerAccessToken).isEmpty()); + WAIT_TEN_SECONDS.until(() -> listMessageIdsForAccount(homerCredential).isEmpty()); exportVaultContent(webAdminApi, EXPORT_ALL_HOMER_MESSAGES_TO_USER_1); @@ -199,37 +201,62 @@ public abstract class LinshareBlobExportMechanismIntegrationTest { } private void bartSendMessageToHomer() { - String messageCreationId = "creationId"; - String outboxId = getOutboxId(bartAccessToken); - String textBody = "You got mail!"; - String requestBody = "[" + - " [" + - " \"setMessages\"," + - " {" + - " \"create\": { \"" + messageCreationId + "\" : {" + - " \"from\": { \"name\": \"user2\", \"email\": \"" + BART + "\"}," + - " \"to\": [{ \"name\": \"user1\", \"email\": \"" + HOMER + "\"}]," + - " \"subject\": \"" + SUBJECT + "\"," + - " \"textBody\": \"" + textBody + "\"," + - " \"htmlBody\": \"Test <b>body</b>, HTML version\"," + - " \"mailboxIds\": [\"" + outboxId + "\"] " + - " }}" + - " }," + - " \"#0\"" + - " ]" + - "]"; + String outboxId = JmapRFCCommonRequests.getOutboxId(bartCredential); + String requestBody = + "{" + + " \"using\": [\"urn:ietf:params:jmap:core\", \"urn:ietf:params:jmap:mail\", \"urn:ietf:params:jmap:submission\"]," + + " \"methodCalls\": [" + + " [\"Email/set\", {" + + " \"accountId\": \"" + bartCredential.accountId() + "\"," + + " \"create\": {" + + " \"e1526\": {" + + " \"mailboxIds\": { \"" + outboxId + "\": true }," + + " \"subject\": \"" + SUBJECT + "\"," + + " \"htmlBody\": [{" + + " \"partId\": \"a49d\"," + + " \"type\": \"text/html\"" + + " }]," + + " \"bodyValues\": {" + + " \"a49d\": {" + + " \"value\": \"Test <b>body</b>, HTML version\"" + + " }" + + " }," + + " \"to\": [{\"email\": \"" + HOMER + "\"}]," + + " \"from\": [{\"email\": \"" + BART + "\"}]" + + " }" + + " }" + + " }, \"c1\"]," + + " [\"Email/get\", {" + + " \"accountId\": \"" + bartCredential.accountId() + "\"," + + " \"ids\": [\"#e1526\"]," + + " \"properties\": [\"sentAt\"]" + + " }, \"c2\"]," + + " [\"EmailSubmission/set\", {" + + " \"accountId\": \"" + bartCredential.accountId() + "\"," + + " \"create\": {" + + " \"k1490\": {" + + " \"emailId\": \"#e1526\"," + + " \"envelope\": {" + + " \"mailFrom\": {\"email\": \"" + BART + "\"}," + + " \"rcptTo\": [{\"email\": \"" + HOMER + "\"}]" + + " }" + + " }" + + " }" + + " }, \"c3\"]" + + " ]" + + "}"; with() - .header("Authorization", bartAccessToken.asString()) + .auth().basic(bartCredential.username().asString(), bartCredential.password()) .body(requestBody) .post("/jmap") - .then() - .extract() - .body() - .path(ARGUMENTS + ".created." + messageCreationId + ".id"); + .then() + .statusCode(200) + .contentType(JSON) + .body("methodResponses[2][1].created", Matchers.is(notNullValue())); } private void homerDeletesMessages(List<String> idsToDestroy) { - deleteMessages(homerAccessToken, idsToDestroy); + deleteMessages(homerCredential, idsToDestroy); } } diff --git a/server/testing/src/main/java/org/apache/james/jmap/JmapRFCCommonRequests.java b/server/testing/src/main/java/org/apache/james/jmap/JmapRFCCommonRequests.java new file mode 100644 index 0000000000..6a1a54a24a --- /dev/null +++ b/server/testing/src/main/java/org/apache/james/jmap/JmapRFCCommonRequests.java @@ -0,0 +1,314 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.jmap; + +import static io.restassured.RestAssured.with; +import static io.restassured.http.ContentType.JSON; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.james.core.Username; +import org.apache.james.mailbox.Role; +import org.hamcrest.Matchers; + +import io.restassured.http.ContentType; +import io.restassured.http.Header; +import io.restassured.path.json.JsonPath; + +public class JmapRFCCommonRequests { + public record UserCredential(Username username, String password, String accountId) { + } + public static final Header ACCEPT_JMAP_RFC_HEADER = new Header("accept", "application/json; jmapVersion=rfc-8621"); + + public static String getOutboxId(UserCredential userCredential) { + return getMailboxId(userCredential, Role.OUTBOX); + } + + public static String getSentId(UserCredential userCredential) { + return getMailboxId(userCredential, Role.SENT); + } + + public static String getDraftId(UserCredential userCredential) { + return getMailboxId(userCredential, Role.DRAFTS); + } + + public static String getMailboxId(UserCredential userCredential, Role role) { + return getAllMailboxesIds(userCredential).stream() + .filter(mailbox -> mailbox.get("role").equals(role.serialize())) + .map(mailbox -> mailbox.get("id")) + .findFirst().orElseThrow(); + } + + public static UserCredential getUserCredential(Username username, String password) { + return getUserCredential(username.asString(), password); + } + + public static UserCredential getUserCredential(String username, String password) { + String accountId = with() + .auth().basic(username, password) + .header(ACCEPT_JMAP_RFC_HEADER) + .get("/jmap/session") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .extract() + .body() + .path("primaryAccounts[\"urn:ietf:params:jmap:core\"]"); + + return new UserCredential(Username.of(username), password, accountId); + } + + public static List<Map<String, String>> getAllMailboxesIds(UserCredential userCredential) { + return with() + .auth().basic(userCredential.username().asString(), userCredential.password()) + .header(ACCEPT_JMAP_RFC_HEADER) + .body(""" + { + "using": [ + "urn:ietf:params:jmap:core", + "urn:ietf:params:jmap:mail", + "urn:apache:james:params:jmap:mail:shares" + ], + "methodCalls": [ + [ + "Mailbox/get", + { + "accountId": "%s" + }, + "c1" + ] + ] + } + """.formatted(userCredential.accountId())) + .post("/jmap") + .andReturn() + .body() + .jsonPath() + .getList("methodResponses[0][1].list"); + } + + public static List<String> listMessageIdsForAccount(UserCredential userCredential) { + return with() + .auth().basic(userCredential.username().asString(), userCredential.password()) + .header(ACCEPT_JMAP_RFC_HEADER) + .body(""" + { + "using": [ + "urn:ietf:params:jmap:core", + "urn:ietf:params:jmap:mail" + ], + "methodCalls": [ + [ + "Email/query", + { + "accountId": "%s", + "sort": [ + { + "isAscending": false, + "property": "receivedAt" + } + ] + }, + "c1" + ] + ] + } + """.formatted(userCredential.accountId())) + .post("/jmap") + .then() + .statusCode(200) + .contentType(JSON) + .extract() + .body() + .path("methodResponses[0][1].ids"); + } + + public static String getLastMessageId(UserCredential userCredential) { + return with() + .auth().basic(userCredential.username().asString(), userCredential.password()) + .header(ACCEPT_JMAP_RFC_HEADER) + .body(""" + { + "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ], + "methodCalls": [ + [ + "Email/query", + { + "accountId": "%s", + "sort": [ { + "isAscending": false, + "property": "receivedAt" + } ] + }, + "c1" + ] + ] + } + """.formatted(userCredential.accountId())) + .post("/jmap") + .then() + .statusCode(200) + .contentType(JSON) + .extract() + .body() + .jsonPath() + .getString("methodResponses[0][1].ids[0]"); + } + + public static Object bodyOfMessage(UserCredential userCredential, String messageId) { + System.out.println("MessageId: " + messageId); + return getMessageContent(userCredential, messageId) + .get("methodResponses[0][1].list[0].textBody"); + } + + public static String getLatestMessageId(UserCredential userCredential, Role mailbox) { + String mailboxId = getMailboxId(userCredential, mailbox); + return with() + .auth().basic(userCredential.username().asString(), userCredential.password()) + .header(ACCEPT_JMAP_RFC_HEADER) + .body(""" + { + "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ], + "methodCalls": [ + [ + "Email/query", + { + "accountId": "%s", + "filter": { + "inMailbox": "%s" + }, + "sort": [ { + "isAscending": false, + "property": "receivedAt" + } ] + }, + "c1" + ] + ] + } + """.formatted(userCredential.accountId(), mailboxId)) + .post("/jmap") + .then() + .statusCode(200) + .contentType(JSON) + .extract() + .body() + .jsonPath() + .getString("methodResponses[0][1].ids[0]"); + } + + + public static JsonPath getMessageContent(UserCredential userCredential, String messageId) { + return with() + .auth().basic(userCredential.username().asString(), userCredential.password()) + .header(ACCEPT_JMAP_RFC_HEADER) + .body(""" + { + "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ], + "methodCalls": [ + [ + "Email/get", + { + "accountId": "%s", + "ids": ["%s"] + }, + "c1" + ] + ] + } + """.formatted(userCredential.accountId(), messageId)) + .when() + .post("/jmap") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .extract() + .jsonPath(); + } + + public static List<String> listMessageIdsInMailbox(UserCredential userCredential, String mailboxId) { + return with() + .auth().basic(userCredential.username().asString(), userCredential.password()) + .header(ACCEPT_JMAP_RFC_HEADER) + .body(""" + { + "using": [ + "urn:ietf:params:jmap:core", + "urn:ietf:params:jmap:mail" + ], + "methodCalls": [ + [ + "Email/query", + { + "accountId": "%s", + "filter": { + "inMailbox": "%s" + }, + "sort": [ + { + "isAscending": false, + "property": "receivedAt" + } + ] + }, + "c1" + ] + ] + }""".formatted(userCredential.accountId(), mailboxId)) + .post("/jmap") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .extract() + .body() + .path("methodResponses[0][1].ids"); + } + + public static void deleteMessages(UserCredential userCredential, List<String> idsToDestroy) { + String idString = idsToDestroy.stream() + .map(id -> "\"" + id + "\"") + .collect(Collectors.joining(",")); + + with() + .auth().basic(userCredential.username().asString(), userCredential.password()) + .header(ACCEPT_JMAP_RFC_HEADER) + .body(""" + { + "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ], + "methodCalls": [ + [ + "Email/set", + { + "accountId": "%s", + "destroy": [%s] + }, + "c1" + ] + ] + }""".formatted(userCredential.accountId(), idString)) + .post("/jmap") + .then() + .statusCode(200) + .contentType(JSON) + .body("methodResponses[0][1].destroyed", Matchers.is(Matchers.hasSize(idsToDestroy.size()))); + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
