Change smtp module to assertk

This commit is contained in:
Wolf Montwé 2023-03-17 14:36:16 +01:00
parent 3f8a3ff0c9
commit 3c9ca12776
No known key found for this signature in database
GPG key ID: 6D45B21512ACBF72
4 changed files with 189 additions and 164 deletions

View file

@ -15,7 +15,6 @@ dependencies {
implementation(libs.commons.io)
implementation(libs.okio)
testImplementation(libs.bundles.shared.jvm.test.legacy)
testImplementation(projects.mail.testing)
testImplementation(libs.okio)
testImplementation(libs.jzlib)

View file

@ -1,9 +1,23 @@
package com.fsck.k9.mail.transport.smtp
import assertk.all
import assertk.assertThat
import assertk.assertions.containsExactly
import assertk.assertions.containsExactlyInAnyOrder
import assertk.assertions.hasMessage
import assertk.assertions.hasSize
import assertk.assertions.isEmpty
import assertk.assertions.isEqualTo
import assertk.assertions.isFailure
import assertk.assertions.isFalse
import assertk.assertions.isInstanceOf
import assertk.assertions.isNotNull
import assertk.assertions.isNull
import assertk.assertions.isTrue
import assertk.assertions.key
import assertk.assertions.prop
import com.fsck.k9.mail.crlf
import com.fsck.k9.mail.filter.PeekableInputStream
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.fail
import org.junit.Test
class SmtpResponseParserTest {
@ -55,21 +69,25 @@ class SmtpResponseParserTest {
val response = parser.readHelloResponse()
assertType<SmtpHelloResponse.Hello>(response) { hello ->
assertThat(hello.response.toLogString(omitText = false, linePrefix = "")).isEqualTo(input)
assertThat(hello.keywords.keys).containsExactly(
"PIPELINING",
"ENHANCEDSTATUSCODES",
"8BITMIME",
"SIZE",
"DELIVERBY",
"AUTH",
"HELP",
)
assertThat(hello.keywords["PIPELINING"]).isEmpty()
assertThat(hello.keywords["SIZE"]).containsExactly("104857600")
assertThat(hello.keywords["AUTH"]).containsExactly("PLAIN", "LOGIN", "CRAM-MD5", "DIGEST-MD5")
assertThat(response).isInstanceOf(SmtpHelloResponse.Hello::class).all {
prop(SmtpHelloResponse.Hello::response)
.transform { it.toLogString(false, "") }.isEqualTo(input)
prop(SmtpHelloResponse.Hello::keywords).all {
transform { it.keys }.containsExactlyInAnyOrder(
"PIPELINING",
"ENHANCEDSTATUSCODES",
"8BITMIME",
"SIZE",
"DELIVERBY",
"AUTH",
"HELP",
)
key("PIPELINING").isNotNull().isEmpty()
key("SIZE").isNotNull().containsExactly("104857600")
key("AUTH").isNotNull().containsExactly("PLAIN", "LOGIN", "CRAM-MD5", "DIGEST-MD5")
}
}
assertInputExhausted(inputStream)
}
@ -80,10 +98,12 @@ class SmtpResponseParserTest {
val response = parser.readHelloResponse()
assertType<SmtpHelloResponse.Hello>(response) { hello ->
assertThat(hello.response.replyCode).isEqualTo(250)
assertThat(hello.response.texts).containsExactly("smtp.domain.example")
assertThat(hello.keywords).isEmpty()
assertThat(response).isInstanceOf(SmtpHelloResponse.Hello::class).all {
prop(SmtpHelloResponse.Hello::response).all {
prop(SmtpResponse::replyCode).isEqualTo(250)
prop(SmtpResponse::texts).containsExactly("smtp.domain.example")
}
prop(SmtpHelloResponse.Hello::keywords).isEmpty()
}
}
@ -94,10 +114,11 @@ class SmtpResponseParserTest {
val response = parser.readHelloResponse()
assertType<SmtpHelloResponse.Error>(response) { error ->
assertThat(error.response.replyCode).isEqualTo(421)
assertThat(error.response.texts).containsExactly("Service not available")
}
assertThat(response).isInstanceOf(SmtpHelloResponse.Error::class)
.prop(SmtpHelloResponse.Error::response).all {
prop(SmtpResponse::replyCode).isEqualTo(421)
prop(SmtpResponse::texts).containsExactly("Service not available")
}
}
@Test
@ -105,9 +126,11 @@ class SmtpResponseParserTest {
val input = "250".toPeekableInputStream()
val parser = SmtpResponseParser(logger, input)
assertFailsWithMessage("Unexpected character: (13)") {
assertThat {
parser.readHelloResponse()
}
}.isFailure()
.isInstanceOf(SmtpResponseParserException::class)
.hasMessage("Unexpected character: (13)")
assertThat(logger.logEntries).containsExactly(
LogEntry(
@ -128,9 +151,11 @@ class SmtpResponseParserTest {
""".toPeekableInputStream()
val parser = SmtpResponseParser(logger, input)
assertFailsWithMessage("Multi-line response with reply codes not matching: 250 != 220") {
assertThat {
parser.readHelloResponse()
}
}.isFailure()
.isInstanceOf(SmtpResponseParserException::class)
.hasMessage("Multi-line response with reply codes not matching: 250 != 220")
assertThat(logger.logEntries).containsExactly(
LogEntry(
@ -159,14 +184,13 @@ class SmtpResponseParserTest {
val response = parser.readHelloResponse()
assertType<SmtpHelloResponse.Hello>(response) { hello ->
assertThat(hello.keywords.keys).containsExactly(
assertThat(response).isInstanceOf(SmtpHelloResponse.Hello::class)
.prop(SmtpHelloResponse.Hello::keywords).transform { it.keys }.containsExactlyInAnyOrder(
"SIZE",
"8BITMIME",
"PIPELINING",
"HELP",
)
}
assertThat(logger.logEntries.map { it.message }).containsExactly(
"Ignoring EHLO keyword line: PIPE_CONNECT",
@ -188,12 +212,11 @@ class SmtpResponseParserTest {
val response = parser.readHelloResponse()
assertType<SmtpHelloResponse.Hello>(response) { hello ->
assertThat(hello.keywords.keys).isEmpty()
}
assertThat(response).isInstanceOf(SmtpHelloResponse.Hello::class)
.transform { it.keywords.keys }.isEmpty()
assertThat(logger.logEntries).hasSize(1)
assertThat(logger.logEntries.first().throwable).hasMessageThat().isEqualTo("EHLO parameter must not be empty")
assertThat(logger.logEntries).isNotNull().hasSize(1)
assertThat(logger.logEntries.first().throwable).isNotNull().hasMessage("EHLO parameter must not be empty")
assertThat(logger.logEntries.first().message).isEqualTo("Ignoring EHLO keyword line: KEYWORD ")
}
@ -208,13 +231,12 @@ class SmtpResponseParserTest {
val response = parser.readHelloResponse()
assertType<SmtpHelloResponse.Hello>(response) { hello ->
assertThat(hello.keywords.keys).containsExactly("8BITMIME")
}
assertThat(response).isInstanceOf(SmtpHelloResponse.Hello::class)
.transform { it.keywords.keys }.containsExactlyInAnyOrder("8BITMIME")
assertThat(logger.logEntries).hasSize(1)
assertThat(logger.logEntries.first().throwable)
.hasMessageThat().isEqualTo("EHLO parameter contains invalid character")
assertThat(logger.logEntries.first().throwable).isNotNull()
.hasMessage("EHLO parameter contains invalid character")
assertThat(logger.logEntries.first().message)
.isEqualTo("Ignoring EHLO keyword line: KEYWORD para${"\t"}meter")
}
@ -228,9 +250,11 @@ class SmtpResponseParserTest {
val parser = SmtpResponseParser(logger, input)
parser.readGreeting()
assertFailsWithMessage("Unexpected character: I (73)") {
assertThat {
parser.readHelloResponse()
}
}.isFailure()
.isInstanceOf(SmtpResponseParserException::class)
.hasMessage("Unexpected character: I (73)")
assertThat(logger.logEntries).containsExactly(
LogEntry(
@ -414,9 +438,11 @@ class SmtpResponseParserTest {
""".toPeekableInputStream()
val parser = SmtpResponseParser(logger, input)
assertFailsWithMessage("Multi-line response with reply codes not matching: 200 != 500") {
assertThat {
parser.readResponse(enhancedStatusCodes = false)
}
}.isFailure()
.isInstanceOf(SmtpResponseParserException::class)
.hasMessage("Multi-line response with reply codes not matching: 200 != 500")
assertThat(logger.logEntries).containsExactly(
LogEntry(
@ -439,10 +465,11 @@ class SmtpResponseParserTest {
val logger = TestSmtpLogger(isRawProtocolLoggingEnabled = false)
val parser = SmtpResponseParser(logger, input)
assertFailsWithMessage("Multi-line response with reply codes not matching: 200 != 500") {
assertThat {
parser.readResponse(enhancedStatusCodes = false)
}
}.isFailure()
.isInstanceOf(SmtpResponseParserException::class)
.hasMessage("Multi-line response with reply codes not matching: 200 != 500")
assertThat(logger.logEntries).isEmpty()
}
@ -451,9 +478,11 @@ class SmtpResponseParserTest {
val input = "611".toPeekableInputStream()
val parser = SmtpResponseParser(logger, input)
assertFailsWithMessage("Unsupported 1st reply code digit: 6") {
assertThat {
parser.readResponse(enhancedStatusCodes = false)
}
}.isFailure()
.isInstanceOf(SmtpResponseParserException::class)
.hasMessage("Unsupported 1st reply code digit: 6")
}
@Test
@ -476,9 +505,11 @@ class SmtpResponseParserTest {
val input = "20x".toPeekableInputStream()
val parser = SmtpResponseParser(logger, input)
assertFailsWithMessage("Unexpected character: x (120)") {
assertThat {
parser.readResponse(enhancedStatusCodes = false)
}
}.isFailure()
.isInstanceOf(SmtpResponseParserException::class)
.hasMessage("Unexpected character: x (120)")
assertThat(logger.logEntries).containsExactly(
LogEntry(
@ -496,9 +527,11 @@ class SmtpResponseParserTest {
val input = PeekableInputStream("200".byteInputStream())
val parser = SmtpResponseParser(logger, input)
assertFailsWithMessage("Unexpected end of stream") {
assertThat {
parser.readResponse(enhancedStatusCodes = false)
}
}.isFailure()
.isInstanceOf(SmtpResponseParserException::class)
.hasMessage("Unexpected end of stream")
assertThat(logger.logEntries).containsExactly(
LogEntry(
@ -516,9 +549,11 @@ class SmtpResponseParserTest {
val input = PeekableInputStream("200\r".byteInputStream())
val parser = SmtpResponseParser(logger, input)
assertFailsWithMessage("Unexpected end of stream") {
assertThat {
parser.readResponse(enhancedStatusCodes = false)
}
}.isFailure()
.isInstanceOf(SmtpResponseParserException::class)
.hasMessage("Unexpected end of stream")
}
@Test
@ -526,9 +561,11 @@ class SmtpResponseParserTest {
val input = PeekableInputStream("200\n".byteInputStream())
val parser = SmtpResponseParser(logger, input)
assertFailsWithMessage("Unexpected character: (10)") {
assertThat {
parser.readResponse(enhancedStatusCodes = false)
}
}.isFailure()
.isInstanceOf(SmtpResponseParserException::class)
.hasMessage("Unexpected character: (10)")
assertThat(logger.logEntries).containsExactly(
LogEntry(
@ -654,12 +691,14 @@ class SmtpResponseParserTest {
""".toPeekableInputStream()
val parser = SmtpResponseParser(logger, input)
assertFailsWithMessage(
"Multi-line response with enhanced status codes not matching: " +
"EnhancedStatusCode(statusClass=PERMANENT_FAILURE, subject=2, detail=1) != null",
) {
assertThat {
parser.readResponse(enhancedStatusCodes = true)
}
}.isFailure()
.isInstanceOf(SmtpResponseParserException::class)
.hasMessage(
"Multi-line response with enhanced status codes not matching: " +
"EnhancedStatusCode(statusClass=PERMANENT_FAILURE, subject=2, detail=1) != null",
)
}
@Test
@ -682,21 +721,7 @@ class SmtpResponseParserTest {
assertThat(input.read()).isEqualTo(-1)
}
private fun assertFailsWithMessage(expectedMessage: String, block: () -> Unit) {
try {
block()
fail("Expected SmtpResponseParserException")
} catch (e: SmtpResponseParserException) {
assertThat(e).hasMessageThat().isEqualTo(expectedMessage)
}
}
private fun String.toPeekableInputStream(): PeekableInputStream {
return PeekableInputStream((this.trimIndent().crlf() + "\r\n").byteInputStream())
}
private inline fun <reified T> assertType(actual: Any, block: (T) -> Unit) {
assertThat(actual).isInstanceOf(T::class.java)
block(actual as T)
}
}

View file

@ -1,6 +1,7 @@
package com.fsck.k9.mail.transport.smtp
import com.google.common.truth.Truth.assertThat
import assertk.assertThat
import assertk.assertions.isEqualTo
import org.junit.Test
class SmtpResponseTest {

View file

@ -1,5 +1,13 @@
package com.fsck.k9.mail.transport.smtp
import assertk.all
import assertk.assertThat
import assertk.assertions.hasMessage
import assertk.assertions.isEqualTo
import assertk.assertions.isFailure
import assertk.assertions.isInstanceOf
import assertk.assertions.isTrue
import assertk.assertions.prop
import com.fsck.k9.mail.AuthType
import com.fsck.k9.mail.AuthenticationFailedException
import com.fsck.k9.mail.CertificateValidationException
@ -14,8 +22,6 @@ import com.fsck.k9.mail.helpers.TestTrustedSocketFactory
import com.fsck.k9.mail.internet.MimeMessage
import com.fsck.k9.mail.oauth.OAuth2TokenProvider
import com.fsck.k9.mail.transport.mockServer.MockSmtpServer
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.fail
import org.junit.Test
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.kotlin.doReturn
@ -116,12 +122,11 @@ class SmtpTransportTest {
}
val transport = startServerAndCreateSmtpTransport(server, authenticationType = AuthType.PLAIN)
try {
assertThat {
transport.open()
fail("Exception expected")
} catch (e: MessagingException) {
assertThat(e).hasMessageThat().isEqualTo("Authentication methods SASL PLAIN and LOGIN are unavailable.")
}
}.isFailure()
.isInstanceOf(MessagingException::class)
.hasMessage("Authentication methods SASL PLAIN and LOGIN are unavailable.")
server.verifyConnectionClosed()
server.verifyInteractionCompleted()
@ -159,12 +164,11 @@ class SmtpTransportTest {
}
val transport = startServerAndCreateSmtpTransport(server, authenticationType = AuthType.CRAM_MD5)
try {
assertThat {
transport.open()
fail("Exception expected")
} catch (e: MessagingException) {
assertThat(e).hasMessageThat().isEqualTo("Authentication method CRAM-MD5 is unavailable.")
}
}.isFailure()
.isInstanceOf(MessagingException::class)
.hasMessage("Authentication method CRAM-MD5 is unavailable.")
server.verifyConnectionClosed()
server.verifyInteractionCompleted()
@ -241,15 +245,14 @@ class SmtpTransportTest {
}
val transport = startServerAndCreateSmtpTransport(server, authenticationType = AuthType.XOAUTH2)
try {
assertThat {
transport.open()
fail("Exception expected")
} catch (e: AuthenticationFailedException) {
assertThat(e).hasMessageThat().isEqualTo(
}.isFailure()
.isInstanceOf(AuthenticationFailedException::class)
.hasMessage(
"5.7.1 Username and Password not accepted. Learn more at " +
"5.7.1 http://support.google.com/mail/bin/answer.py?answer=14257 hx9sm5317360pbc.68",
)
}
inOrder(oAuth2TokenProvider) {
verify(oAuth2TokenProvider).getToken(anyLong())
@ -366,15 +369,14 @@ class SmtpTransportTest {
val transport = startServerAndCreateSmtpTransport(server, authenticationType = AuthType.XOAUTH2)
try {
assertThat {
transport.open()
fail("Exception expected")
} catch (e: AuthenticationFailedException) {
assertThat(e).hasMessageThat().isEqualTo(
}.isFailure()
.isInstanceOf(AuthenticationFailedException::class)
.hasMessage(
"5.7.1 Username and Password not accepted. Learn more at " +
"5.7.1 http://support.google.com/mail/bin/answer.py?answer=14257 hx9sm5317360pbc.68",
)
}
server.verifyConnectionClosed()
server.verifyInteractionCompleted()
@ -395,12 +397,11 @@ class SmtpTransportTest {
}
val transport = startServerAndCreateSmtpTransport(server, authenticationType = AuthType.XOAUTH2)
try {
assertThat {
transport.open()
fail("Exception expected")
} catch (e: AuthenticationFailedException) {
assertThat(e).hasMessageThat().isEqualTo("Failed to fetch token")
}
}.isFailure()
.isInstanceOf(AuthenticationFailedException::class)
.hasMessage("Failed to fetch token")
server.verifyConnectionClosed()
server.verifyInteractionCompleted()
@ -418,12 +419,11 @@ class SmtpTransportTest {
}
val transport = startServerAndCreateSmtpTransport(server, authenticationType = AuthType.XOAUTH2)
try {
assertThat {
transport.open()
fail("Exception expected")
} catch (e: MessagingException) {
assertThat(e).hasMessageThat().isEqualTo("Server doesn't support SASL OAUTHBEARER or XOAUTH2.")
}
}.isFailure()
.isInstanceOf(MessagingException::class)
.hasMessage("Server doesn't support SASL OAUTHBEARER or XOAUTH2.")
server.verifyConnectionClosed()
server.verifyInteractionCompleted()
@ -459,12 +459,12 @@ class SmtpTransportTest {
}
val transport = startServerAndCreateSmtpTransport(server, authenticationType = AuthType.EXTERNAL)
try {
assertThat {
transport.open()
fail("Exception expected")
} catch (e: CertificateValidationException) {
assertThat(e.reason).isEqualTo(CertificateValidationException.Reason.MissingCapability)
}
}.isFailure()
.isInstanceOf(CertificateValidationException::class)
.prop(CertificateValidationException::getReason)
.isEqualTo(CertificateValidationException.Reason.MissingCapability)
server.verifyConnectionClosed()
server.verifyInteractionCompleted()
@ -509,14 +509,11 @@ class SmtpTransportTest {
connectionSecurity = ConnectionSecurity.NONE,
)
try {
assertThat {
transport.open()
fail("Exception expected")
} catch (e: MessagingException) {
assertThat(e).hasMessageThat().isEqualTo(
"Update your outgoing server authentication setting. AUTOMATIC authentication is unavailable.",
)
}
}.isFailure()
.isInstanceOf(MessagingException::class)
.hasMessage("Update your outgoing server authentication setting. AUTOMATIC authentication is unavailable.")
server.verifyConnectionClosed()
server.verifyInteractionCompleted()
@ -556,15 +553,14 @@ class SmtpTransportTest {
server.output("221 BYE")
val transport = startServerAndCreateSmtpTransport(server, authenticationType = AuthType.XOAUTH2)
try {
assertThat {
transport.open()
fail("Exception expected")
} catch (e: AuthenticationFailedException) {
assertThat(e).hasMessageThat().isEqualTo(
}.isFailure()
.isInstanceOf(AuthenticationFailedException::class)
.hasMessage(
"Username and Password not accepted. " +
"Learn more at http://support.google.com/mail/bin/answer.py?answer=14257 hx9sm5317360pbc.68",
)
}
inOrder(oAuth2TokenProvider) {
verify(oAuth2TokenProvider).getToken(anyLong())
@ -722,13 +718,13 @@ class SmtpTransportTest {
val server = createServerAndSetupForPlainAuthentication("SIZE 1000")
val transport = startServerAndCreateSmtpTransport(server)
try {
assertThat {
transport.sendMessage(message)
fail("Expected message too large error")
} catch (e: MessagingException) {
assertThat(e.isPermanentFailure).isTrue()
assertThat(e).hasMessageThat().isEqualTo("Message too large for server")
}
}.isFailure()
.isInstanceOf(MessagingException::class).all {
hasMessage("Message too large for server")
transform { it.isPermanentFailure }.isTrue()
}
// FIXME: Make sure connection was closed
// server.verifyConnectionClosed();
@ -753,13 +749,13 @@ class SmtpTransportTest {
}
val transport = startServerAndCreateSmtpTransport(server)
try {
assertThat {
transport.sendMessage(message)
fail("Expected exception")
} catch (e: NegativeSmtpReplyException) {
assertThat(e.replyCode).isEqualTo(421)
assertThat(e.replyText).isEqualTo("4.7.0 Temporary system problem")
}
}.isFailure()
.isInstanceOf(NegativeSmtpReplyException::class).all {
prop(NegativeSmtpReplyException::replyCode).isEqualTo(421)
prop(NegativeSmtpReplyException::replyText).isEqualTo("4.7.0 Temporary system problem")
}
server.verifyConnectionClosed()
server.verifyInteractionCompleted()
@ -829,13 +825,14 @@ class SmtpTransportTest {
}
val transport = startServerAndCreateSmtpTransport(server)
try {
assertThat {
transport.sendMessage(message)
fail("Expected exception")
} catch (e: NegativeSmtpReplyException) {
assertThat(e.replyCode).isEqualTo(550)
assertThat(e.replyText).isEqualTo("remote mail to <user2@localhost> not allowed")
}
}.isFailure()
.isInstanceOf(NegativeSmtpReplyException::class).all {
prop(NegativeSmtpReplyException::replyCode).isEqualTo(550)
prop(NegativeSmtpReplyException::replyText)
.isEqualTo("remote mail to <user2@localhost> not allowed")
}
server.verifyConnectionClosed()
server.verifyInteractionCompleted()
@ -854,13 +851,14 @@ class SmtpTransportTest {
server.closeConnection()
val transport = startServerAndCreateSmtpTransport(server)
try {
assertThat {
transport.sendMessage(message)
fail("Expected exception")
} catch (e: NegativeSmtpReplyException) {
assertThat(e.replyCode).isEqualTo(550)
assertThat(e.replyText).isEqualTo("remote mail to <user2@localhost> not allowed")
}
}.isFailure()
.isInstanceOf(NegativeSmtpReplyException::class).all {
prop(NegativeSmtpReplyException::replyCode).isEqualTo(550)
prop(NegativeSmtpReplyException::replyText)
.isEqualTo("remote mail to <user2@localhost> not allowed")
}
server.verifyConnectionClosed()
server.verifyInteractionCompleted()
@ -882,13 +880,14 @@ class SmtpTransportTest {
}
val transport = startServerAndCreateSmtpTransport(server)
try {
assertThat {
transport.sendMessage(message)
fail("Expected exception")
} catch (e: NegativeSmtpReplyException) {
assertThat(e.replyCode).isEqualTo(550)
assertThat(e.replyText).isEqualTo("remote mail to <user2@localhost> not allowed")
}
}.isFailure()
.isInstanceOf(NegativeSmtpReplyException::class).all {
prop(NegativeSmtpReplyException::replyCode).isEqualTo(550)
prop(NegativeSmtpReplyException::replyText)
.isEqualTo("remote mail to <user2@localhost> not allowed")
}
server.verifyConnectionClosed()
server.verifyInteractionCompleted()
@ -910,13 +909,14 @@ class SmtpTransportTest {
}
val transport = startServerAndCreateSmtpTransport(server)
try {
assertThat {
transport.sendMessage(message)
fail("Expected exception")
} catch (e: NegativeSmtpReplyException) {
assertThat(e.replyCode).isEqualTo(550)
assertThat(e.replyText).isEqualTo("remote mail to <user3@localhost> not allowed")
}
}.isFailure()
.isInstanceOf(NegativeSmtpReplyException::class).all {
prop(NegativeSmtpReplyException::replyCode).isEqualTo(550)
prop(NegativeSmtpReplyException::replyText)
.isEqualTo("remote mail to <user3@localhost> not allowed")
}
server.verifyConnectionClosed()
server.verifyInteractionCompleted()