blob: e82847672e3cca834ad50dcbd02cf12ada1302c6 [file] [log] [blame]
/****************************************************************
* 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.mailbox.cassandra.mail;
import static org.apache.james.backends.cassandra.Scenario.Builder.fail;
import static org.apache.james.mailbox.model.MailboxAssertingTool.softly;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.util.stream.IntStream;
import org.apache.commons.lang3.StringUtils;
import org.apache.james.backends.cassandra.CassandraCluster;
import org.apache.james.backends.cassandra.CassandraClusterExtension;
import org.apache.james.backends.cassandra.Scenario;
import org.apache.james.backends.cassandra.components.CassandraModule;
import org.apache.james.backends.cassandra.init.configuration.CassandraConfiguration;
import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionDAO;
import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionModule;
import org.apache.james.backends.cassandra.versions.SchemaVersion;
import org.apache.james.core.Username;
import org.apache.james.eventsourcing.eventstore.cassandra.CassandraEventStore;
import org.apache.james.eventsourcing.eventstore.cassandra.CassandraEventStoreModule;
import org.apache.james.eventsourcing.eventstore.cassandra.EventStoreDao;
import org.apache.james.eventsourcing.eventstore.JsonEventSerializer;
import org.apache.james.junit.categories.Unstable;
import org.apache.james.mailbox.cassandra.ids.CassandraId;
import org.apache.james.mailbox.cassandra.mail.eventsourcing.acl.ACLModule;
import org.apache.james.mailbox.cassandra.modules.CassandraAclModule;
import org.apache.james.mailbox.cassandra.modules.CassandraMailboxModule;
import org.apache.james.mailbox.exception.MailboxNotFoundException;
import org.apache.james.mailbox.exception.TooLongMailboxNameException;
import org.apache.james.mailbox.model.Mailbox;
import org.apache.james.mailbox.model.MailboxId;
import org.apache.james.mailbox.model.MailboxPath;
import org.apache.james.mailbox.model.UidValidity;
import org.apache.james.mailbox.model.search.ExactName;
import org.apache.james.mailbox.model.search.MailboxQuery;
import org.apache.james.mailbox.model.search.Wildcard;
import org.apache.james.mailbox.store.MailboxReactorUtils;
import org.assertj.core.api.SoftAssertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import com.github.fge.lambdas.Throwing;
import com.github.fge.lambdas.runnable.ThrowingRunnable;
import reactor.core.publisher.Mono;
class CassandraMailboxMapperTest {
private static final UidValidity UID_VALIDITY = UidValidity.of(52);
private static final Username USER = Username.of("user");
private static final CassandraId MAILBOX_ID = CassandraId.timeBased();
private static final MailboxPath MAILBOX_PATH = MailboxPath.forUser(USER, "name");
private static final Mailbox MAILBOX = new Mailbox(MAILBOX_PATH, UID_VALIDITY, MAILBOX_ID);
private static final String INBOX = "INBOX";
private static final String INBOX_RENAMED = "INBOX_RENAMED";
private static final CassandraId MAILBOX_ID_2 = CassandraId.timeBased();
private static final Mailbox MAILBOX_BIS = new Mailbox(MAILBOX_PATH, UID_VALIDITY, MAILBOX_ID_2);
private static final CassandraModule MODULES = CassandraModule.aggregateModules(
CassandraAclModule.MODULE,
CassandraEventStoreModule.MODULE(),
CassandraMailboxModule.MODULE,
CassandraSchemaVersionModule.MODULE);
private static final int TRY_COUNT_BEFORE_FAILURE = 6;
@RegisterExtension
static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(MODULES);
private CassandraMailboxDAO mailboxDAO;
private CassandraMailboxPathV3DAO mailboxPathV3DAO;
private CassandraMailboxMapper testee;
private CassandraSchemaVersionDAO versionDAO;
@BeforeEach
void setUp() {
CassandraCluster cassandra = cassandraCluster.getCassandraCluster();
mailboxDAO = new CassandraMailboxDAO(cassandra.getConf(), cassandra.getTypesProvider());
mailboxPathV3DAO = new CassandraMailboxPathV3DAO(cassandra.getConf());
versionDAO = new CassandraSchemaVersionDAO(cassandra.getConf());
versionDAO.truncateVersion()
.then(versionDAO.updateVersion(new SchemaVersion(7)))
.block();
setUpTestee(CassandraConfiguration.DEFAULT_CONFIGURATION);
}
private void setUpTestee(CassandraConfiguration cassandraConfiguration) {
CassandraCluster cassandra = cassandraCluster.getCassandraCluster();
CassandraACLDAOV2 aclDAOv2 = new CassandraACLDAOV2(cassandra.getConf());
JsonEventSerializer jsonEventSerializer = JsonEventSerializer
.forModules(ACLModule.ACL_UPDATE)
.withoutNestedType();
CassandraUserMailboxRightsDAO usersRightDAO = new CassandraUserMailboxRightsDAO(cassandra.getConf());
CassandraEventStore eventStore = new CassandraEventStore(new EventStoreDao(cassandra.getConf(), jsonEventSerializer));
CassandraACLMapper aclMapper = new CassandraACLMapper(
new CassandraACLMapper.StoreV2(usersRightDAO, aclDAOv2, eventStore),
CassandraConfiguration.DEFAULT_CONFIGURATION);
testee = new CassandraMailboxMapper(
mailboxDAO,
mailboxPathV3DAO,
usersRightDAO,
aclMapper,
cassandraConfiguration);
}
@Nested
class ConsistencyTest {
private MailboxPath inboxPath;
private MailboxPath inboxPathRenamed;
private MailboxQuery.UserBound allMailboxesSearchQuery;
private MailboxQuery.UserBound inboxSearchQuery;
private MailboxQuery.UserBound inboxRenamedSearchQuery;
@BeforeEach
void setUp() {
inboxPath = MailboxPath.forUser(USER, INBOX);
inboxPathRenamed = MailboxPath.forUser(USER, INBOX_RENAMED);
allMailboxesSearchQuery = MailboxQuery.builder()
.userAndNamespaceFrom(inboxPath)
.expression(Wildcard.INSTANCE)
.build()
.asUserBound();
inboxSearchQuery = MailboxQuery.builder()
.userAndNamespaceFrom(inboxPath)
.expression(new ExactName(INBOX))
.build()
.asUserBound();
inboxRenamedSearchQuery = MailboxQuery.builder()
.userAndNamespaceFrom(inboxPathRenamed)
.expression(new ExactName(INBOX_RENAMED))
.build()
.asUserBound();
}
@Nested
class Retries {
@Test
void renameShouldRetryFailedDeleteMailboxPath(CassandraCluster cassandra) {
Mailbox inbox = testee.create(inboxPath, UID_VALIDITY).block();
MailboxId inboxId = inbox.getMailboxId();
Mailbox inboxRenamed = createInboxRenamedMailbox(inboxId);
cassandra.getConf()
.registerScenario(fail()
.times(1)
.whenQueryStartsWith("DELETE FROM mailboxpathv2"));
testee.rename(inboxRenamed).block();
SoftAssertions.assertSoftly(Throwing.consumer(softly -> {
softly(softly)
.assertThat(testee.findMailboxById(inboxId).block())
.isEqualTo(inboxRenamed);
softly(softly)
.assertThat(testee.findMailboxByPath(inboxPathRenamed).block())
.isEqualTo(inboxRenamed);
softly.assertThat(testee.findMailboxWithPathLike(allMailboxesSearchQuery)
.collectList().block())
.hasOnlyOneElementSatisfying(searchMailbox -> softly(softly)
.assertThat(searchMailbox)
.isEqualTo(inboxRenamed));
}));
}
@Test
void renameShouldRetryFailedMailboxSaving(CassandraCluster cassandra) {
Mailbox inbox = testee.create(inboxPath, UID_VALIDITY).block();
MailboxId inboxId = inbox.getMailboxId();
Mailbox inboxRenamed = createInboxRenamedMailbox(inboxId);
cassandra.getConf()
.registerScenario(fail()
.times(1)
.whenQueryStartsWith("INSERT INTO mailbox (id,name,uidvalidity,mailboxbase)"));
testee.rename(inboxRenamed).block();
SoftAssertions.assertSoftly(Throwing.consumer(softly -> {
softly(softly)
.assertThat(testee.findMailboxById(inboxId).block())
.isEqualTo(inboxRenamed);
softly(softly)
.assertThat(testee.findMailboxByPath(inboxPathRenamed).block())
.isEqualTo(inboxRenamed);
softly.assertThat(testee.findMailboxWithPathLike(allMailboxesSearchQuery)
.collectList().block())
.hasOnlyOneElementSatisfying(searchMailbox -> softly(softly)
.assertThat(searchMailbox)
.isEqualTo(inboxRenamed));
}));
}
@Test
void createShouldRetryFailedMailboxSaving(CassandraCluster cassandra) {
cassandra.getConf()
.registerScenario(fail()
.times(1)
.whenQueryStartsWith("INSERT INTO mailbox (id,name,uidvalidity,mailboxbase)"));
Mailbox inbox = testee.create(inboxPath, UID_VALIDITY).block();
SoftAssertions.assertSoftly(Throwing.consumer(softly -> {
softly(softly)
.assertThat(testee.findMailboxById(inbox.getMailboxId()).block())
.isEqualTo(inbox);
softly(softly)
.assertThat(testee.findMailboxByPath(inboxPath).block())
.isEqualTo(inbox);
softly.assertThat(testee.findMailboxWithPathLike(allMailboxesSearchQuery)
.collectList().block())
.hasOnlyOneElementSatisfying(searchMailbox -> softly(softly)
.assertThat(searchMailbox)
.isEqualTo(inbox));
}));
}
@Test
void deleteShouldRetryFailedMailboxDeletion(CassandraCluster cassandra) {
Mailbox inbox = testee.create(inboxPath, UID_VALIDITY).block();
cassandra.getConf()
.registerScenario(fail()
.times(1)
.whenQueryStartsWith("DELETE FROM mailbox "));
testee.delete(inbox).block();
SoftAssertions.assertSoftly(Throwing.consumer(softly -> {
softly.assertThatThrownBy(() -> testee.findMailboxById(inbox.getMailboxId()).block())
.hasCauseInstanceOf(MailboxNotFoundException.class);
softly.assertThat(testee.findMailboxByPath(inboxPath).blockOptional())
.isEmpty();
softly.assertThat(testee.findMailboxWithPathLike(allMailboxesSearchQuery)
.collectList().block())
.isEmpty();
}));
}
}
@Nested
class ReadRepairs {
@BeforeEach
void setVersion() {
// Read repairs should not be performed with an outdated data representation
versionDAO.truncateVersion()
.then(versionDAO.updateVersion(new SchemaVersion(8)))
.block();
}
@Test
void findMailboxByIdShouldEventuallyFixInconsistencyWhenMailboxIsNotInPath() {
mailboxDAO.save(MAILBOX)
.block();
IntStream.range(0, 100).forEach(i ->
testee.findMailboxById(MAILBOX_ID)
.onErrorResume(e -> Mono.empty())
.block());
SoftAssertions.assertSoftly(Throwing.consumer(softly -> {
softly(softly)
.assertThat(testee.findMailboxById(MAILBOX_ID).block())
.isEqualTo(MAILBOX);
softly(softly)
.assertThat(testee.findMailboxByPath(MAILBOX_PATH).block())
.isEqualTo(MAILBOX);
}));
}
@Test
void orphanMailboxIdEntriesCanNotBeReadRepaired() {
mailboxDAO.save(MAILBOX)
.block();
IntStream.range(0, 100).forEach(i ->
testee.findMailboxByPath(MAILBOX_PATH)
.onErrorResume(e -> Mono.empty())
.block());
SoftAssertions.assertSoftly(Throwing.consumer(softly -> {
softly.assertThat(MailboxReactorUtils.blockOptional(testee.findMailboxByPath(MAILBOX_PATH)))
.isEmpty();
softly(softly)
.assertThat(testee.findMailboxById(MAILBOX_ID).block())
.isEqualTo(MAILBOX);
}));
}
@Test
void orphanPathEntriesCanNotBeRepairedByIdReads() {
mailboxPathV3DAO.save(MAILBOX)
.block();
IntStream.range(0, 100).forEach(i ->
testee.findMailboxById(MAILBOX_ID)
.onErrorResume(e -> Mono.empty())
.block());
SoftAssertions.assertSoftly(Throwing.consumer(softly -> {
softly.assertThatThrownBy(() -> MailboxReactorUtils.blockOptional(testee.findMailboxById(MAILBOX_ID)))
.isInstanceOf(MailboxNotFoundException.class);
softly(softly)
.assertThat(testee.findMailboxByPath(MAILBOX_PATH).block())
.isEqualTo(MAILBOX);
}));
}
@Test
void findMailboxByPathShouldFixInconsistencyWhenMailboxIsNotReferencedById() {
mailboxPathV3DAO.save(MAILBOX)
.block();
IntStream.range(0, 100).forEach(i ->
testee.findMailboxByPath(MAILBOX_PATH)
.onErrorResume(e -> Mono.empty())
.block());
SoftAssertions.assertSoftly(Throwing.consumer(softly -> {
softly.assertThatThrownBy(() -> MailboxReactorUtils.blockOptional(testee.findMailboxById(MAILBOX_ID)))
.isInstanceOf(MailboxNotFoundException.class);
softly.assertThat(MailboxReactorUtils.blockOptional(testee.findMailboxByPath(MAILBOX_PATH)))
.isEmpty();
}));
}
}
@Disabled("In order to be more performant mailboxPath V3 table includes the UID_VALIDITY." +
"Reading paths no longer requires reading the mailbox by id but this of course has a " +
"consistency cost.")
@Test
void createShouldBeConsistentWhenFailToPersistMailbox(CassandraCluster cassandra) {
cassandra.getConf()
.registerScenario(fail()
.times(10)
.whenQueryStartsWith("INSERT INTO mailbox (id,name,uidvalidity,mailboxbase)"));
doQuietly(() -> testee.create(inboxPath, UID_VALIDITY).block());
SoftAssertions.assertSoftly(softly -> {
softly.assertThat(testee.findMailboxByPath(inboxPath).blockOptional())
.isEmpty();
softly.assertThat(testee.findMailboxWithPathLike(inboxSearchQuery)
.collectList().block())
.isEmpty();
softly.assertThat(testee.findMailboxWithPathLike(allMailboxesSearchQuery)
.collectList().block())
.isEmpty();
});
}
@Test
void renameThenFailToRetrieveMailboxShouldBeConsistentWhenFindByInbox(CassandraCluster cassandra) {
Mailbox inbox = testee.create(inboxPath, UID_VALIDITY).block();
CassandraId inboxId = (CassandraId) inbox.getMailboxId();
Mailbox inboxRenamed = createInboxRenamedMailbox(inboxId);
cassandra.getConf()
.registerScenario(fail()
.times(TRY_COUNT_BEFORE_FAILURE)
.whenQueryStartsWith("SELECT id,mailboxbase,uidvalidity,name FROM mailbox"));
doQuietly(() -> testee.rename(inboxRenamed));
cassandra.getConf().registerScenario(Scenario.NOTHING);
SoftAssertions.assertSoftly(Throwing.consumer(softly -> {
softly(softly)
.assertThat(testee.findMailboxById(inboxId).block())
.isEqualTo(inbox);
softly(softly)
.assertThat(testee.findMailboxByPath(inboxPath).block())
.isEqualTo(inbox);
softly.assertThat(testee.findMailboxWithPathLike(inboxSearchQuery)
.collectList().block())
.hasOnlyOneElementSatisfying(searchMailbox -> softly(softly)
.assertThat(searchMailbox)
.isEqualTo(inbox));
}));
}
@Disabled("JAMES-3056 returning two mailboxes with same name and id")
@Test
void renameThenFailToRetrieveMailboxShouldBeConsistentWhenFindAll(CassandraCluster cassandra) {
Mailbox inbox = testee.create(inboxPath, UID_VALIDITY).block();
CassandraId inboxId = (CassandraId) inbox.getMailboxId();
Mailbox inboxRenamed = createInboxRenamedMailbox(inboxId);
cassandra.getConf()
.registerScenario(fail()
.times(TRY_COUNT_BEFORE_FAILURE)
.whenQueryStartsWith("SELECT id,mailboxbase,uidvalidity,name FROM mailbox"));
doQuietly(() -> testee.rename(inboxRenamed).block());
SoftAssertions.assertSoftly(Throwing.consumer(softly ->
softly.assertThat(testee.findMailboxWithPathLike(allMailboxesSearchQuery)
.collectList().block())
.hasOnlyOneElementSatisfying(searchMailbox -> softly(softly)
.assertThat(searchMailbox)
.isEqualTo(inbox))
));
}
@Disabled("JAMES-3056 find by renamed name returns unexpected results")
@Test
void renameThenFailToRetrieveMailboxShouldBeConsistentWhenFindByRenamedInbox(CassandraCluster cassandra) {
Mailbox inbox = testee.create(inboxPath, UID_VALIDITY).block();
CassandraId inboxId = (CassandraId) inbox.getMailboxId();
Mailbox inboxRenamed = createInboxRenamedMailbox(inboxId);
cassandra.getConf()
.registerScenario(fail()
.times(TRY_COUNT_BEFORE_FAILURE)
.whenQueryStartsWith("SELECT id,mailboxbase,uidvalidity,name FROM mailbox WHERE id=:id"));
doQuietly(() -> testee.rename(inboxRenamed).block());
SoftAssertions.assertSoftly(Throwing.consumer(softly -> {
softly.assertThatThrownBy(() -> testee.findMailboxByPath(inboxPathRenamed).block())
.isInstanceOf(MailboxNotFoundException.class);
softly.assertThat(testee.findMailboxWithPathLike(inboxRenamedSearchQuery)
.collectList().block())
.isEmpty();
}));
}
@Test
void renameThenFailToDeleteMailboxPathShouldBeConsistentWhenFindByInbox(CassandraCluster cassandra) {
Mailbox inbox = testee.create(inboxPath, UID_VALIDITY).block();
CassandraId inboxId = (CassandraId) inbox.getMailboxId();
Mailbox inboxRenamed = createInboxRenamedMailbox(inboxId);
cassandra.getConf()
.registerScenario(fail()
.times(TRY_COUNT_BEFORE_FAILURE)
.whenQueryStartsWith("DELETE FROM mailboxpathv3 WHERE namespace=:namespace AND user=:user AND mailboxname=:mailboxname IF EXISTS"));
doQuietly(() -> testee.rename(inboxRenamed).block());
SoftAssertions.assertSoftly(Throwing.consumer(softly -> {
softly(softly)
.assertThat(testee.findMailboxById(inboxId).block())
.isEqualTo(inbox);
softly(softly)
.assertThat(testee.findMailboxByPath(inboxPath).block())
.isEqualTo(inbox);
softly.assertThat(testee.findMailboxWithPathLike(inboxSearchQuery)
.collectList().block())
.hasOnlyOneElementSatisfying(searchMailbox -> softly(softly)
.assertThat(searchMailbox)
.isEqualTo(inbox));
}));
}
@Disabled("JAMES-3056 returning two mailboxes with same name and id")
@Test
void renameThenFailToDeleteMailboxPathShouldBeConsistentWhenFindAll(CassandraCluster cassandra) {
Mailbox inbox = testee.create(inboxPath, UID_VALIDITY).block();
CassandraId inboxId = (CassandraId) inbox.getMailboxId();
Mailbox inboxRenamed = createInboxRenamedMailbox(inboxId);
cassandra.getConf()
.registerScenario(fail()
.times(TRY_COUNT_BEFORE_FAILURE)
.whenQueryStartsWith("DELETE FROM mailboxpathv3 WHERE namespace=:namespace AND user=:user AND mailboxname=:mailboxname IF EXISTS"));
doQuietly(() -> testee.rename(inboxRenamed).block());
SoftAssertions.assertSoftly(Throwing.consumer(softly ->
softly.assertThat(testee.findMailboxWithPathLike(allMailboxesSearchQuery)
.collectList().block())
.hasOnlyOneElementSatisfying(searchMailbox -> softly(softly)
.assertThat(searchMailbox)
.isEqualTo(inbox))));
}
@Disabled("JAMES-3056 find by renamed name returns unexpected results")
@Test
void renameThenFailToDeleteMailboxPathShouldBeConsistentWhenFindByRenamedInbox(CassandraCluster cassandra) {
Mailbox inbox = testee.create(inboxPath, UID_VALIDITY).block();
CassandraId inboxId = (CassandraId) inbox.getMailboxId();
Mailbox inboxRenamed = createInboxRenamedMailbox(inboxId);
cassandra.getConf()
.registerScenario(fail()
.times(TRY_COUNT_BEFORE_FAILURE)
.whenQueryStartsWith("DELETE FROM mailboxpathv3 WHERE namespace=:namespace AND user=:user AND mailboxname=:mailboxname IF EXISTS"));
doQuietly(() -> testee.rename(inboxRenamed).block());
SoftAssertions.assertSoftly(Throwing.consumer(softly -> {
softly.assertThatThrownBy(() -> testee.findMailboxByPath(inboxPathRenamed).block())
.isInstanceOf(MailboxNotFoundException.class);
softly.assertThat(testee.findMailboxWithPathLike(inboxRenamedSearchQuery)
.collectList().block())
.isEmpty();
}));
}
@Disabled("JAMES-3056 find by mailbox name returns unexpected results")
@Test
void deleteShouldBeConsistentWhenFailToDeleteMailbox(CassandraCluster cassandra) {
Mailbox inbox = testee.create(inboxPath, UID_VALIDITY).block();
CassandraId inboxId = (CassandraId) inbox.getMailboxId();
cassandra.getConf()
.registerScenario(fail()
.times(TRY_COUNT_BEFORE_FAILURE)
.whenQueryStartsWith("DELETE FROM mailbox WHERE id=:id"));
doQuietly(() -> testee.delete(inbox).block());
SoftAssertions.assertSoftly(Throwing.consumer(softly -> {
softly.assertThatCode(() -> testee.findMailboxById(inboxId).block())
.doesNotThrowAnyException();
softly.assertThatCode(() -> testee.findMailboxByPath(inboxPath).block())
.doesNotThrowAnyException();
softly.assertThat(testee.findMailboxWithPathLike(inboxSearchQuery)
.collectList().block())
.hasOnlyOneElementSatisfying(searchMailbox -> softly(softly)
.assertThat(searchMailbox)
.isEqualTo(inbox));
softly.assertThat(testee.findMailboxWithPathLike(allMailboxesSearchQuery)
.collectList().block())
.hasOnlyOneElementSatisfying(searchMailbox -> softly(softly)
.assertThat(searchMailbox)
.isEqualTo(inbox));
}));
}
@Disabled("JAMES-3056 org.apache.james.mailbox.exception.MailboxNotFoundException: 'mailboxId' can not be found")
@Test
void createAfterPreviousFailedCreateShouldCreateAMailbox(CassandraCluster cassandra) {
cassandra.getConf()
.registerScenario(fail()
.times(TRY_COUNT_BEFORE_FAILURE)
.whenQueryStartsWith("INSERT INTO mailbox (id,name,uidvalidity,mailboxbase) VALUES (:id,:name,:uidvalidity,:mailboxbase)"));
doQuietly(() -> testee.create(inboxPath, UID_VALIDITY).block());
Mailbox inbox = testee.create(inboxPath, UID_VALIDITY).block();
SoftAssertions.assertSoftly(Throwing.consumer(softly -> {
softly(softly)
.assertThat(testee.findMailboxByPath(inboxPath).block())
.isEqualTo(inbox);
softly.assertThat(testee.findMailboxWithPathLike(inboxSearchQuery)
.collectList().block())
.hasOnlyOneElementSatisfying(searchMailbox -> softly(softly)
.assertThat(searchMailbox)
.isEqualTo(inbox));
softly.assertThat(testee.findMailboxWithPathLike(allMailboxesSearchQuery)
.collectList().block())
.hasOnlyOneElementSatisfying(searchMailbox -> softly(softly)
.assertThat(searchMailbox)
.isEqualTo(inbox));
}));
}
@Test
/*
https://builds.apache.org/blue/organizations/jenkins/james%2FApacheJames/detail/PR-268/38/tests
Expected size:<1> but was:<2> in:
<[Mailbox{id=44ec7c50-405c-11eb-bd9e-f9735674a69e, namespace=#private, user=Username{localPart=user, domainPart=Optional.empty}, name=INBOX},
Mailbox{id=4282f660-405c-11eb-bd9e-f9735674a69e, namespace=#private, user=Username{localPart=user, domainPart=Optional.empty}, name=name}]>
at CassandraMailboxMapperTest$ConsistencyTest.lambda$createAfterPreviousDeleteOnFailedCreateShouldCreateAMailbox$34(CassandraMailboxMapperTest$ConsistencyTest.java:628)
*/
@Tag(Unstable.TAG)
void createAfterPreviousDeleteOnFailedCreateShouldCreateAMailbox(CassandraCluster cassandra) {
cassandra.getConf()
.registerScenario(fail()
.times(TRY_COUNT_BEFORE_FAILURE)
.whenQueryStartsWith("INSERT INTO mailbox (id,name,uidvalidity,mailboxbase) VALUES (:id,:name,:uidvalidity,:mailboxbase)"));
doQuietly(() -> testee.create(inboxPath, UID_VALIDITY).block());
doQuietly(() -> testee.delete(new Mailbox(inboxPath, UID_VALIDITY, CassandraId.timeBased())).block());
Mailbox inbox = testee.create(inboxPath, UID_VALIDITY).block();
SoftAssertions.assertSoftly(Throwing.consumer(softly -> {
softly(softly)
.assertThat(testee.findMailboxByPath(inboxPath).block())
.isEqualTo(inbox);
softly.assertThat(testee.findMailboxWithPathLike(inboxSearchQuery)
.collectList().block())
.hasOnlyOneElementSatisfying(searchMailbox -> softly(softly)
.assertThat(searchMailbox)
.isEqualTo(inbox));
softly.assertThat(testee.findMailboxWithPathLike(allMailboxesSearchQuery)
.collectList().block())
.hasOnlyOneElementSatisfying(searchMailbox -> softly(softly)
.assertThat(searchMailbox)
.isEqualTo(inbox));
}));
}
@Test
void deleteAfterAFailedDeleteShouldDeleteTheMailbox(CassandraCluster cassandra) {
Mailbox inbox = testee.create(inboxPath, UID_VALIDITY).block();
CassandraId inboxId = (CassandraId) inbox.getMailboxId();
cassandra.getConf()
.registerScenario(fail()
.times(TRY_COUNT_BEFORE_FAILURE)
.whenQueryStartsWith("DELETE FROM mailbox WHERE id=:id"));
doQuietly(() -> testee.delete(inbox).block());
doQuietly(() -> testee.delete(inbox).block());
SoftAssertions.assertSoftly(Throwing.consumer(softly -> {
softly.assertThatThrownBy(() -> testee.findMailboxById(inboxId).block())
.hasCauseInstanceOf(MailboxNotFoundException.class);
softly.assertThat(testee.findMailboxByPath(inboxPath).blockOptional())
.isEmpty();
softly.assertThat(testee.findMailboxWithPathLike(inboxSearchQuery)
.collectList().block())
.isEmpty();
softly.assertThat(testee.findMailboxWithPathLike(allMailboxesSearchQuery)
.collectList().block())
.isEmpty();
}));
}
@Disabled("JAMES-3056 mailbox name is not updated to INBOX_RENAMED).isEqualTo(" +
"findMailboxWithPathLike() returns a list with two same mailboxes")
@Test
void renameAfterRenameFailOnRetrieveMailboxShouldRenameTheMailbox(CassandraCluster cassandra) {
Mailbox inbox = testee.create(inboxPath, UID_VALIDITY).block();
CassandraId inboxId = (CassandraId) inbox.getMailboxId();
Mailbox inboxRenamed = createInboxRenamedMailbox(inboxId);
cassandra.getConf()
.registerScenario(fail()
.times(TRY_COUNT_BEFORE_FAILURE)
.whenQueryStartsWith("SELECT id,mailboxbase,uidvalidity,name FROM mailbox WHERE id=:id"));
doQuietly(() -> testee.rename(inboxRenamed).block());
doQuietly(() -> testee.rename(inboxRenamed).block());
SoftAssertions.assertSoftly(Throwing.consumer(softly -> {
softly(softly)
.assertThat(testee.findMailboxById(inboxId).block())
.isEqualTo(inboxRenamed);
softly(softly)
.assertThat(testee.findMailboxByPath(inboxPathRenamed).block())
.isEqualTo(inboxRenamed);
softly.assertThat(testee.findMailboxWithPathLike(inboxSearchQuery)
.collectList().block())
.isEmpty();
softly.assertThat(testee.findMailboxWithPathLike(inboxRenamedSearchQuery)
.collectList().block())
.hasOnlyOneElementSatisfying(searchMailbox -> softly(softly)
.assertThat(searchMailbox)
.isEqualTo(inboxRenamed));
softly.assertThat(testee.findMailboxWithPathLike(allMailboxesSearchQuery)
.collectList().block())
.hasOnlyOneElementSatisfying(searchMailbox -> softly(softly)
.assertThat(searchMailbox)
.isEqualTo(inboxRenamed));
}));
}
@Disabled("JAMES-3056 mailbox name is not updated to INBOX_RENAMED")
@Test
void renameAfterRenameFailOnDeletePathShouldRenameTheMailbox(CassandraCluster cassandra) {
Mailbox inbox = testee.create(inboxPath, UID_VALIDITY).block();
CassandraId inboxId = (CassandraId) inbox.getMailboxId();
Mailbox inboxRenamed = createInboxRenamedMailbox(inboxId);
cassandra.getConf()
.registerScenario(fail()
.times(TRY_COUNT_BEFORE_FAILURE)
.whenQueryStartsWith("DELETE FROM mailboxpathv3 WHERE namespace=:namespace AND user=:user AND mailboxname=:mailboxname IF EXISTS"));
doQuietly(() -> testee.rename(inboxRenamed).block());
doQuietly(() -> testee.rename(inboxRenamed).block());
SoftAssertions.assertSoftly(Throwing.consumer(softly -> {
softly(softly)
.assertThat(testee.findMailboxById(inboxId).block())
.isEqualTo(inboxRenamed);
softly(softly)
.assertThat(testee.findMailboxByPath(inboxPathRenamed).block())
.isEqualTo(inboxRenamed);
softly.assertThat(testee.findMailboxWithPathLike(inboxSearchQuery)
.collectList().block())
.isEmpty();
softly.assertThat(testee.findMailboxWithPathLike(inboxRenamedSearchQuery)
.collectList().block())
.hasOnlyOneElementSatisfying(searchMailbox ->
softly(softly)
.assertThat(searchMailbox)
.isEqualTo(inboxRenamed));
softly.assertThat(testee.findMailboxWithPathLike(allMailboxesSearchQuery)
.collectList().block())
.hasOnlyOneElementSatisfying(searchMailbox -> softly(softly)
.assertThat(searchMailbox)
.isEqualTo(inboxRenamed));
}));
}
private void doQuietly(ThrowingRunnable runnable) {
try {
runnable.run();
} catch (Throwable th) {
// ignore
}
}
private Mailbox createInboxRenamedMailbox(MailboxId mailboxId) {
return new Mailbox(inboxPathRenamed, UID_VALIDITY, mailboxId);
}
}
@Disabled("JAMES-2514 Cassandra 3 supports long mailbox names. Hence we can not rely on this for failing")
@Test
void renameShouldNotRemoveOldMailboxPathWhenCreatingTheNewMailboxPathFails() {
testee.create(MAILBOX_PATH, UID_VALIDITY).block();
Mailbox mailbox = testee.findMailboxByPath(MAILBOX_PATH).block();
Mailbox newMailbox = new Mailbox(tooLongMailboxPath(mailbox.generateAssociatedPath()), UID_VALIDITY, mailbox.getMailboxId());
assertThatThrownBy(() -> testee.rename(newMailbox).block())
.isInstanceOf(TooLongMailboxNameException.class);
assertThat(mailboxPathV3DAO.retrieve(MAILBOX_PATH).blockOptional())
.isPresent();
}
private MailboxPath tooLongMailboxPath(MailboxPath fromMailboxPath) {
return new MailboxPath(fromMailboxPath, StringUtils.repeat("b", 65537));
}
}