Inject address signature in RE/FWD case

MAILANDR-498
This commit is contained in:
Adam Jodlowski
2023-10-17 21:33:16 +02:00
parent 1871b63a4d
commit 9dac7edc07
4 changed files with 206 additions and 6 deletions
+1
View File
@@ -13,6 +13,7 @@
</list>
</option>
<option name="enableDetekt" value="true" />
<option name="enableForProjectResult" value="Accepted" />
<option name="enableFormatting" value="true" />
<option name="pluginJarPaths">
<set>
@@ -21,4 +21,13 @@ package ch.protonmail.android.mailcomposer.domain.model
data class AddressSignature(
val html: String,
val plaintext: String
)
) {
companion object {
const val SeparatorPlaintext = "\n\n\n"
val BlankSignature = AddressSignature("", "")
}
}
@@ -20,11 +20,13 @@ package ch.protonmail.android.mailcomposer.presentation.usecase
import android.content.Context
import arrow.core.Either
import arrow.core.getOrElse
import arrow.core.left
import arrow.core.right
import ch.protonmail.android.mailcommon.domain.model.DataError
import ch.protonmail.android.mailcommon.presentation.model.TextUiModel
import ch.protonmail.android.mailcommon.presentation.usecase.FormatExtendedTime
import ch.protonmail.android.mailcomposer.domain.model.AddressSignature
import ch.protonmail.android.mailcomposer.domain.model.DraftAction
import ch.protonmail.android.mailcomposer.domain.model.DraftBody
import ch.protonmail.android.mailcomposer.domain.model.DraftFields
@@ -35,6 +37,7 @@ import ch.protonmail.android.mailcomposer.domain.model.RecipientsCc
import ch.protonmail.android.mailcomposer.domain.model.RecipientsTo
import ch.protonmail.android.mailcomposer.domain.model.SenderEmail
import ch.protonmail.android.mailcomposer.domain.model.Subject
import ch.protonmail.android.mailcomposer.domain.usecase.GetAddressSignature
import ch.protonmail.android.mailcomposer.domain.usecase.ObserveUserAddresses
import ch.protonmail.android.mailcomposer.presentation.R
import ch.protonmail.android.mailmessage.domain.model.DecryptedMessageBody
@@ -53,7 +56,8 @@ import kotlin.time.Duration.Companion.seconds
class ParentMessageToDraftFields @Inject constructor(
@ApplicationContext private val context: Context,
private val observeUserAddresses: ObserveUserAddresses,
private val formatExtendedTime: FormatExtendedTime
private val formatExtendedTime: FormatExtendedTime,
private val getAddressSignature: GetAddressSignature
) {
suspend operator fun invoke(
@@ -65,11 +69,12 @@ class ParentMessageToDraftFields @Inject constructor(
val decryptedBody = messageWithDecryptedBody.decryptedMessageBody
val userAddresses = observeUserAddresses(userId).firstOrNull() ?: return DataError.Local.NoDataCached.left()
val sender = getSenderEmail(userAddresses, message)
val senderAddressSignature = getAddressSignature(userId, sender).getOrElse { AddressSignature.BlankSignature }
return DraftFields(
sender,
Subject("${subjectPrefixForAction(action)} ${message.subject}"),
buildQuotedPlainTextBody(message, decryptedBody),
buildQuotedPlainTextBody(message, decryptedBody, senderAddressSignature),
RecipientsTo(recipientsForAction(action, messageWithDecryptedBody.messageWithBody, sender)),
RecipientsCc(ccRecipientsForAction(action, message)),
RecipientsBcc(emptyList()),
@@ -77,8 +82,15 @@ class ParentMessageToDraftFields @Inject constructor(
).right()
}
private fun buildQuotedPlainTextBody(message: Message, decryptedBody: DecryptedMessageBody): DraftBody {
if (decryptedBody.mimeType != MimeType.PlainText) {
private fun buildQuotedPlainTextBody(
message: Message,
decryptedBody: DecryptedMessageBody,
senderAddressSignature: AddressSignature
): DraftBody {
if (decryptedBody.mimeType != MimeType.PlainText && senderAddressSignature.plaintext.isNotBlank()) {
// HTML quote is fully created elsewhere, but we still need to inject signature into editable body
return DraftBody("${AddressSignature.SeparatorPlaintext}${senderAddressSignature.plaintext}")
} else if (decryptedBody.mimeType != MimeType.PlainText) {
return DraftBody("")
}
@@ -88,6 +100,8 @@ class ParentMessageToDraftFields @Inject constructor(
.joinToString(separator = PlainTextNewLine) { "$PlainTextQuotePrefix $it" }
val raw = StringBuilder()
.append(if (senderAddressSignature.plaintext.isNotBlank()) AddressSignature.SeparatorPlaintext else "")
.append(if (senderAddressSignature.plaintext.isNotBlank()) senderAddressSignature.plaintext else "")
.append(PlainTextNewLine)
.append(PlainTextNewLine)
.append(PlainTextNewLine)
@@ -20,13 +20,17 @@ package ch.protonmail.android.mailcomposer.presentation.usecase
import android.content.Context
import androidx.annotation.StringRes
import arrow.core.right
import ch.protonmail.android.mailcommon.domain.sample.UserAddressSample
import ch.protonmail.android.mailcommon.domain.sample.UserIdSample
import ch.protonmail.android.mailcommon.presentation.model.TextUiModel
import ch.protonmail.android.mailcommon.presentation.usecase.FormatExtendedTime
import ch.protonmail.android.mailcomposer.domain.model.AddressSignature
import ch.protonmail.android.mailcomposer.domain.model.DraftAction
import ch.protonmail.android.mailcomposer.domain.model.MessageWithDecryptedBody
import ch.protonmail.android.mailcomposer.domain.model.SenderEmail
import ch.protonmail.android.mailcomposer.domain.model.Subject
import ch.protonmail.android.mailcomposer.domain.usecase.GetAddressSignature
import ch.protonmail.android.mailcomposer.domain.usecase.ObserveUserAddresses
import ch.protonmail.android.mailcomposer.presentation.R
import ch.protonmail.android.mailcomposer.presentation.usecase.ParentMessageToDraftFields.Companion.CloseProtonMailBlockquote
@@ -39,6 +43,7 @@ import ch.protonmail.android.mailmessage.domain.sample.MessageSample
import ch.protonmail.android.mailmessage.domain.sample.MessageWithBodySample
import ch.protonmail.android.mailmessage.domain.sample.RecipientSample
import ch.protonmail.android.testdata.message.DecryptedMessageBodyTestData
import io.mockk.coEvery
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.flow.flowOf
@@ -56,6 +61,7 @@ class ParentMessageToDraftFieldsTest {
private val observeUserAddresses = mockk<ObserveUserAddresses>()
private val context = mockk<Context>()
private val formatTime = mockk<FormatExtendedTime>()
private val getAddressSignatureMock = mockk<GetAddressSignature>()
private val expectedOriginalMessageRes = expectStringRes(R.string.composer_original_message_quote) {
"Original Message"
@@ -67,7 +73,8 @@ class ParentMessageToDraftFieldsTest {
private val parentMessageToDraftFields = ParentMessageToDraftFields(
context,
observeUserAddresses,
formatTime
formatTime,
getAddressSignatureMock
)
@Test
@@ -90,6 +97,7 @@ class ParentMessageToDraftFieldsTest {
)
expectedUserAddresses(userId) { listOf(UserAddressSample.PrimaryAddress) }
val expectedBody = expectedDecryptedMessage.decryptedMessageBody.value
expectBlankSignatureForSenderAddress(userId, SenderEmail(UserAddressSample.PrimaryAddress.email))
// When
val actual = parentMessageToDraftFields(userId, expectedDecryptedMessage, expectedAction).getOrNull()!!
@@ -111,6 +119,152 @@ class ParentMessageToDraftFieldsTest {
assertEquals(expectedQuotedHtmlBody, actual.originalHtmlQuote?.value)
}
@Test
fun `returns draft body with injected sender signature for plaintext message`() = runTest {
// Given
val userId = UserIdSample.Primary
val expectedAction = DraftAction.Reply(MessageIdSample.Invoice)
val expectedDecryptedMessage = MessageWithDecryptedBody(
MessageWithBodySample.Invoice,
DecryptedMessageBodyTestData.PlainTextDecryptedBody
)
val expectedTime = expectFormattedTime(MessageSample.Invoice.time.seconds) {
TextUiModel.Text("Sep 13, 2023 3:36 PM")
}
val expectedOriginalMessageQuote = "-------- $expectedOriginalMessageRes --------"
val expectedSenderQuote = expectedSenderQuoteRes.format(
expectedTime.value,
expectedDecryptedMessage.messageWithBody.message.sender.name,
expectedDecryptedMessage.messageWithBody.message.sender.address
)
expectedUserAddresses(userId) { listOf(UserAddressSample.PrimaryAddress) }
val expectedBody = "${ParentMessageToDraftFields.PlainTextQuotePrefix} " +
expectedDecryptedMessage.decryptedMessageBody.value
val expectedSignature = expectSignatureForSenderAddress(
userId,
SenderEmail(UserAddressSample.PrimaryAddress.email)
)
// When
val actual = parentMessageToDraftFields(userId, expectedDecryptedMessage, expectedAction).getOrNull()!!
// Then
val expectedQuotedPlaintextBody = StringBuilder()
.append(ParentMessageToDraftFields.PlainTextNewLine)
.append(ParentMessageToDraftFields.PlainTextNewLine)
.append(ParentMessageToDraftFields.PlainTextNewLine)
.append(expectedSignature.plaintext)
.append(ParentMessageToDraftFields.PlainTextNewLine)
.append(ParentMessageToDraftFields.PlainTextNewLine)
.append(ParentMessageToDraftFields.PlainTextNewLine)
.append(expectedOriginalMessageQuote)
.append(ParentMessageToDraftFields.PlainTextNewLine)
.append(expectedSenderQuote)
.append(ParentMessageToDraftFields.PlainTextNewLine)
.append(ParentMessageToDraftFields.PlainTextNewLine)
.append(expectedBody)
.toString()
assertEquals(expectedQuotedPlaintextBody, actual.body.value)
}
@Test
fun `returns draft body with injected blank sender signature for plaintext message`() = runTest {
// Given
val userId = UserIdSample.Primary
val expectedAction = DraftAction.Reply(MessageIdSample.Invoice)
val expectedDecryptedMessage = MessageWithDecryptedBody(
MessageWithBodySample.Invoice,
DecryptedMessageBodyTestData.PlainTextDecryptedBody
)
val expectedTime = expectFormattedTime(MessageSample.Invoice.time.seconds) {
TextUiModel.Text("Sep 13, 2023 3:36 PM")
}
val expectedOriginalMessageQuote = "-------- $expectedOriginalMessageRes --------"
val expectedSenderQuote = expectedSenderQuoteRes.format(
expectedTime.value,
expectedDecryptedMessage.messageWithBody.message.sender.name,
expectedDecryptedMessage.messageWithBody.message.sender.address
)
expectedUserAddresses(userId) { listOf(UserAddressSample.PrimaryAddress) }
val expectedBody = "${ParentMessageToDraftFields.PlainTextQuotePrefix} " +
expectedDecryptedMessage.decryptedMessageBody.value
expectBlankSignatureForSenderAddress(
userId,
SenderEmail(UserAddressSample.PrimaryAddress.email)
)
// When
val actual = parentMessageToDraftFields(userId, expectedDecryptedMessage, expectedAction).getOrNull()!!
// Then
val expectedQuotedPlaintextBody = StringBuilder()
.append(ParentMessageToDraftFields.PlainTextNewLine)
.append(ParentMessageToDraftFields.PlainTextNewLine)
.append(ParentMessageToDraftFields.PlainTextNewLine)
.append(expectedOriginalMessageQuote)
.append(ParentMessageToDraftFields.PlainTextNewLine)
.append(expectedSenderQuote)
.append(ParentMessageToDraftFields.PlainTextNewLine)
.append(ParentMessageToDraftFields.PlainTextNewLine)
.append(expectedBody)
.toString()
assertEquals(expectedQuotedPlaintextBody, actual.body.value)
}
@Test
fun `returns draft body with injected sender signature for HTML message`() = runTest {
// Given
val userId = UserIdSample.Primary
val expectedAction = DraftAction.Reply(MessageIdSample.HtmlInvoice)
val expectedDecryptedMessage = MessageWithDecryptedBody(
MessageWithBodySample.HtmlInvoice,
DecryptedMessageBodyTestData.htmlInvoice
)
expectFormattedTime(MessageSample.HtmlInvoice.time.seconds) {
TextUiModel.Text("Sep 13, 2023 3:36 PM")
}
expectedUserAddresses(userId) { listOf(UserAddressSample.PrimaryAddress) }
val expectedSignature = expectSignatureForSenderAddress(
userId,
SenderEmail(UserAddressSample.PrimaryAddress.email)
)
val expectedBody = "${AddressSignature.SeparatorPlaintext}${expectedSignature.plaintext}"
// When
val actual = parentMessageToDraftFields(userId, expectedDecryptedMessage, expectedAction).getOrNull()!!
// Then
assertEquals(expectedBody, actual.body.value)
}
@Test
fun `returns draft body with injected blank sender signature for HTML message`() = runTest {
// Given
val userId = UserIdSample.Primary
val expectedAction = DraftAction.Reply(MessageIdSample.HtmlInvoice)
val expectedDecryptedMessage = MessageWithDecryptedBody(
MessageWithBodySample.HtmlInvoice,
DecryptedMessageBodyTestData.htmlInvoice
)
expectFormattedTime(MessageSample.HtmlInvoice.time.seconds) {
TextUiModel.Text("Sep 13, 2023 3:36 PM")
}
expectedUserAddresses(userId) { listOf(UserAddressSample.PrimaryAddress) }
expectBlankSignatureForSenderAddress(
userId,
SenderEmail(UserAddressSample.PrimaryAddress.email)
)
val expectedBody = ""
// When
val actual = parentMessageToDraftFields(userId, expectedDecryptedMessage, expectedAction).getOrNull()!!
// Then
assertEquals(expectedBody, actual.body.value)
}
@Test
fun `returns draft data with prefixed subject based on draft action`() = runTest {
// Given
@@ -122,6 +276,7 @@ class ParentMessageToDraftFieldsTest {
)
expectedUserAddresses(userId) { listOf(UserAddressSample.PrimaryAddress) }
expectFormattedTime(MessageSample.HtmlInvoice.time.seconds) { TextUiModel.Text("Sep 13, 2023 3:36 PM") }
expectBlankSignatureForSenderAddress(userId, SenderEmail(UserAddressSample.PrimaryAddress.email))
// When
val actual = parentMessageToDraftFields(userId, expectedDecryptedMessage, expectedAction).getOrNull()!!
@@ -142,6 +297,7 @@ class ParentMessageToDraftFieldsTest {
)
expectedUserAddresses(userId) { listOf(UserAddressSample.PrimaryAddress) }
expectFormattedTime(MessageSample.HtmlInvoice.time.seconds) { TextUiModel.Text("Sep 13, 2023 3:36 PM") }
expectBlankSignatureForSenderAddress(userId, SenderEmail(UserAddressSample.PrimaryAddress.email))
// When
val actual = parentMessageToDraftFields(userId, expectedDecryptedMessage, expectedAction).getOrNull()!!
@@ -168,6 +324,7 @@ class ParentMessageToDraftFieldsTest {
)
expectedUserAddresses(userId) { listOf(UserAddressSample.PrimaryAddress) }
expectFormattedTime(MessageSample.HtmlInvoice.time.seconds) { TextUiModel.Text("Sep 13, 2023 3:36 PM") }
expectBlankSignatureForSenderAddress(userId, SenderEmail(UserAddressSample.PrimaryAddress.email))
// When
val actual = parentMessageToDraftFields(userId, expectedDecryptedMessage, expectedAction).getOrNull()!!
@@ -196,6 +353,7 @@ class ParentMessageToDraftFieldsTest {
)
expectedUserAddresses(userId) { listOf(johnUserAddress) }
expectFormattedTime(MessageSample.HtmlInvoice.time.seconds) { TextUiModel.Text("Sep 13, 2023 3:36 PM") }
expectBlankSignatureForSenderAddress(userId, SenderEmail(johnUserAddress.email))
// When
val actual = parentMessageToDraftFields(userId, expectedDecryptedMessage, expectedAction).getOrNull()!!
@@ -217,4 +375,22 @@ class ParentMessageToDraftFieldsTest {
private fun expectedUserAddresses(userId: UserId, addresses: () -> List<UserAddress>) = addresses().also {
every { observeUserAddresses.invoke(userId) } returns flowOf(it)
}
private fun expectSignatureForSenderAddress(
expectedUserId: UserId,
expectedSenderEmail: SenderEmail
): AddressSignature = AddressSignature(
"<div>HTML signature</div>",
"Plaintext signature"
).also {
coEvery { getAddressSignatureMock(expectedUserId, expectedSenderEmail) } returns it.right()
}
private fun expectBlankSignatureForSenderAddress(
expectedUserId: UserId,
expectedSenderEmail: SenderEmail
): AddressSignature = AddressSignature(
"",
""
).also { coEvery { getAddressSignatureMock(expectedUserId, expectedSenderEmail) } returns it.right() }
}