New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JAMES-2214 Draft saving #1120
JAMES-2214 Draft saving #1120
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some comments along the way, mostly about naming.
Overall, I'm happy to see that things are fitting quite well in existing codebase and that you managed to do that so quickly !
@@ -279,8 +258,10 @@ private MessageWithId handleOutboxMessages(CreationMessageEntry entry, MailboxSe | |||
if (!isRequestForSending(entry.getValue(), session)) { | |||
throw new IllegalStateException("Messages for everything but outbox should have been filtered earlier"); | |||
} | |||
MetaDataWithContent newMessage = createMessageInOutbox(entry, outbox, session); | |||
return sendMessage(entry.getCreationId(), newMessage, session); | |||
MetaDataWithContent newMessage = messageAppender.createMessageInMailbox(entry, outbox, session); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the previous version was simpler to understand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes but see later on: we need a valid Jmap message when saving a draft : hence I came back and did not factorize JMAP message computation.
Moreover think to the Big Picture: That extraction is ready for draft moving to outbox.
this.mimeMessageConverter = mimeMessageConverter; | ||
} | ||
|
||
public MessageFactory.MetaDataWithContent createMessageInMailbox(ValueWithId.MessageWithId.CreationMessageEntry createdEntry, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
appendMessageInMailbox
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you wish
} | ||
|
||
public MessageFactory.MetaDataWithContent createMessageInMailbox(ValueWithId.MessageWithId.CreationMessageEntry createdEntry, | ||
MessageManager mailbox, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if it's a jmap API, why don't you take a MailboxId here ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well SystemMailboxes provider directly gives MessageManager.
- No need to perform an extra lookup
- We already have it in caller code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what caller code ? you don't know the caller, you are creating an API.
Or maybe it's a private class used by a single consumer ?
Otherwise, please make the API as consistent as you would like to find it as a developper browsing the API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
benoit, if you want to keep this optimization you can provide the 2 methods, one with MessageId, and one with the MessageManager.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok for duplicated methods of course.
Ps: I don't concider this really an optimization
.
It is more I have a superset of MailboxId, I wish to be calling the API with my superset of MailboxId, not doing a .getMailboxId.
this.mimeMessageConverter = mimeMessageConverter; | ||
} | ||
|
||
public MessageFactory.MetaDataWithContent createMessageInMailbox(ValueWithId.MessageWithId.CreationMessageEntry createdEntry, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you import ValueWithId so that we only read MessageWithId here ?
this.mailFactory = mailFactory; | ||
} | ||
|
||
public void sendMessage(Message jmapMessage, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how are the two first argument related ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One is generated from the other.
You have 3 related things:
- CreationMessage
- MessageMetadata: once you saved it in store
- JMAP message is used only for the enveloppe.
We shoudl have the MailFactory taking an Enveloppe instead of a full JMAP message.
} | ||
|
||
@Test | ||
public void setMessageShouldRejectCreateInDraftAndOutbox() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...CreateInBothDraftAndOutboxForASingleMessage
} | ||
|
||
@Test | ||
public void setMessageShouldRequireDraftFlag() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in any case or just in Draft mailbox ?
MessageWithId created = handleOutboxMessages(create, mailboxSession); | ||
responseBuilder.created(created.getCreationId(), created.getValue()); | ||
|
||
performCreate(create, responseBuilder, mailboxSession); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
handleCreate is calling performCreate.
I think that bad naming scheme reflect the lack of proper abstraction. What was wrong with this version ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not like the double check to the feature:
- 1 check to see if it's draft OR outbox, 1 second check to perform the feature (that was always sending).
Generally, error management is performed in a separated method. Is that a bad thing? I tend to concider separating these concerns is good.
responseBuilder.created(created.getCreationId(), created.getValue()); | ||
} | ||
|
||
private void validateDraftKeywords(CreationMessageEntry entry) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assert
@@ -1396,6 +1396,40 @@ public void setMessageShouldRequireDraftInOnlyDraftMailbox() { | |||
} | |||
|
|||
@Test | |||
public void setMessageShouldBeCompatibleWithIsDraftProperty() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what does it mean ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have two distinct way to interact with flags:
- Through **is* ** properties
- Through keywords object
I just want to check INBOX will be able to save a message with isDraft, as they do not support keywords yet.
I find this name explicit. Any proposal for improvment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I propose " check INBOX will be able to save a message with isDraft,"
I'm kidding but "should be compatible" doesn't say what you explained.
So setMessageShouldAllowDraftCreationWhenUsingIsDraftProperty
?
private Mail buildMessage(MessageFactory.MetaDataWithContent message, Message jmapMessage) throws MessagingException { | ||
try { | ||
return mailFactory.build(message, jmapMessage); | ||
} catch (IOException e) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you wrap the IOException directly in MailFactory.build
?
@@ -363,6 +364,11 @@ private void assertValidFromProvided(ImmutableList.Builder<ValidationResult> err | |||
public boolean isIn(MessageManager mailbox) { | |||
return mailboxIds.contains(mailbox.getId().serialize()); | |||
} | |||
|
|||
public boolean isInOnly(MessageManager mailbox) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isTheOnlyOne
or highlander
} | ||
|
||
public MessageFactory.MetaDataWithContent createMessageInMailbox(ValueWithId.MessageWithId.CreationMessageEntry createdEntry, | ||
MessageManager mailbox, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
benoit, if you want to keep this optimization you can provide the 2 methods, one with MessageId, and one with the MessageManager.
.isInline(attachment.isIsInline()) | ||
.build()); | ||
} catch (AttachmentNotFoundException e) { | ||
// should not happen (checked before) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this comment still true?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably not, but it can be safely removed I assume.
session)) | ||
.isInstanceOf(AttachmentsNotFoundException.class) | ||
.matches(e -> ((AttachmentsNotFoundException)e).getAttachmentIds().containsAll(ImmutableSet.of(unknownBlobId1, unknownBlobId2))); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you have 2 negative test cases, can you add positive test cases?
assertAttachmentExistsShouldNotThrowWhenKnownBlobId
assertAttachmentsExistShouldNotThrowWhenKnownBlobIds
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We would also need the mix between positive and negative.
Ps: I did not do it as I only intended to move code around.
} | ||
|
||
@Test | ||
public void setMessageShouldNotCheckFromWhenDraft() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
setMessages (with a s at the end), and same remark in every test case
" \"setMessages\","+ | ||
" {" + | ||
" \"create\": { \"" + messageCreationId + "\" : {" + | ||
" \"from\": { \"name\": \"Me\", \"email\": \"invalid@domain.com\"}," + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you also add a test case with an email without "@"?
@@ -294,25 +292,6 @@ public void processShouldNotSpoolMailWhenNotSavingToOutbox() throws Exception { | |||
} | |||
|
|||
@Test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you don't need to add any unit test? (yes I've seen that you did a great coverage in integration tests)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With mocking?
Sorry, but it ties down to much SetMessagesCreationProcessor with lower layers, I really don't like it.
If you ask me for it, I prefer porting these tests to rely on InMemoryMailboxManager through InMemoryIntegrationResousources.
BTW now coverage with unit test will be duplication, isn't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was just a question. It's not really needed as you are doing all these integration tests. Sometime you need to add some unit tests (so yes with mocking) to check special behaviour such as storage returning some exceptions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have coverage for the error management I introduced, and BTW extended it ;-)
I don't think we end up in that scenario.
@@ -300,6 +303,7 @@ public void processShouldNotSendWhenSavingToDrafts() throws Exception { | |||
.create( | |||
creationMessageId, creationMessageBuilder.mailboxId(DRAFTS_ID.serialize()).build()) | |||
.build(); | |||
when(mockedMailboxManager.getMailbox(any(MailboxId.class), any())).thenReturn(drafts); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
c/r
@@ -53,6 +53,11 @@ public StoreAttachmentManager(AttachmentMapperFactory attachmentMapperFactory, M | |||
} | |||
|
|||
@Override | |||
public boolean exists(AttachmentId attachmentId, MailboxSession session) throws MailboxException { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no existing test for AttachmentManager, only tests on mapper.
As testing this might require either:
- Creating generic tests (AbstractAttachmentManagerTests blah)
- Relying on mocking (there is a little test with mocking, but I really don't like mocking)
So I would go for the first one, if you realy want it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no that's ok, manager is just wiring so we can go without these tests
@@ -58,7 +59,7 @@ public MessageAppender(AttachmentManager attachmentManager, MIMEMessageConverter | |||
this.mimeMessageConverter = mimeMessageConverter; | |||
} | |||
|
|||
public MessageFactory.MetaDataWithContent appendMessageInMailbox(ValueWithId.MessageWithId.CreationMessageEntry createdEntry, | |||
public MessageFactory.MetaDataWithContent appendMessageInMailbox(CreationMessageEntry createdEntry, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the remark was about importing ValueWithId, not MessageWithId, so you keep MessageWithId.CreationMessageEntry
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I don't get it. Naming CreationMessageEntry seems to convey enough meaning, I believe we can get a ride of MessageWithId
|
||
public MailboxSendingNotAllowedException(Collection<String> allowedFroms) { | ||
public MailboxSendingNotAllowedException(String allowedFroms) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/Froms/From/
test this please |
I do not succeed to reproduce CI issue locally:
|
Still the CI error:
|
} | ||
|
||
@Test | ||
public void setMessagesShouldBeCompatibleWithIsDraftProperty() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you didn't change this method name based on my suggestion ?
import com.google.common.base.Preconditions; | ||
import com.google.common.base.Throwables; | ||
|
||
public class Envelope { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure about the choosen wording, Envelope usually includes headers and here that's not the case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you have a better idea? I can not come up with a better name....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would strip Sender and call the remaining fields "Recipients".
LOGGER.error("Invalid mail address", emailer.getEmail()); | ||
throw Throwables.propagate(e); | ||
} | ||
Sets.union(envelope.getTo(), envelope.getCc()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
envelope.listRecipients ?
@@ -101,4 +117,49 @@ public void assertAttachmentsExistShouldThrowWhenUnknownBlobIds() throws Mailbox | |||
.matches(e -> ((AttachmentsNotFoundException)e).getAttachmentIds().containsAll(ImmutableSet.of(unknownBlobId1, unknownBlobId2))); | |||
} | |||
|
|||
@Test | |||
public void assertAttachmentsExistShouldNotThrowThrowWhenKnownBlobIds() throws Exception { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/ThrowThrow/Throw/
a222f59
to
feac3b1
Compare
This is required as recipients should not be validated when saving a draft, but only when sending it.
When we reached the code about saving message in Outbox, we already enforced the message should only be in outbox, hence that code was not needed.
Before, messageIds was stated, which was wrong. That error properties was not tested.
MailboxRightsException leads to think it concerns mailbox rights, but was raised when having rights on a delegated mailbox
…tion MailboxNotImplementedException and the "Not yet implemented" message is misleading. JMAP spec does not support, whatever version you take, saving messages in other mailboxes than Outbox or Draft. The error message name should explain that the given mailboxes were invalid. Moreover, the error message should explain which state was not met by the provided mailboxes.
- More fluent API - Avoid retrieving full attachment content for existence checks
… an Envelope and not a full JMAP Message
We need a more readable way to concat (n) streams than chaining Stream.concat calls
No description provided.