mirror of
https://github.com/VinylDNS/vinyldns
synced 2025-08-22 02:02:14 +00:00
Add email notifier (#674)
* Add email notifier Provide email on batch change to the requesting user * Test email notifier Add unit tests for email notifier * Address EmailNotifier comments Add integration test for Email Notifier Log unparseable emails Add detail to email
This commit is contained in:
parent
c880b07145
commit
3074e503fa
@ -32,3 +32,10 @@ services:
|
|||||||
- "9324:9324"
|
- "9324:9324"
|
||||||
volumes:
|
volumes:
|
||||||
- ./elasticmq/custom.conf:/etc/elasticmq/elasticmq.conf
|
- ./elasticmq/custom.conf:/etc/elasticmq/elasticmq.conf
|
||||||
|
|
||||||
|
mail:
|
||||||
|
image: flaviovs/mock-smtp
|
||||||
|
ports:
|
||||||
|
- "19025:25"
|
||||||
|
volumes:
|
||||||
|
- ../target:/var/lib/mock-smtp
|
||||||
|
@ -93,6 +93,10 @@ vinyldns {
|
|||||||
crypto {
|
crypto {
|
||||||
type = "vinyldns.core.crypto.NoOpCrypto"
|
type = "vinyldns.core.crypto.NoOpCrypto"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
email.settings.smtp {
|
||||||
|
port = 19025
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Global settings
|
# Global settings
|
||||||
|
@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 Comcast Cable Communications Management, LLC
|
||||||
|
*
|
||||||
|
* Licensed 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 vinyldns.api.notifier.email
|
||||||
|
|
||||||
|
import com.typesafe.config.{Config, ConfigFactory}
|
||||||
|
import vinyldns.core.notifier._
|
||||||
|
import vinyldns.api.MySqlApiIntegrationSpec
|
||||||
|
import vinyldns.mysql.MySqlIntegrationSpec
|
||||||
|
import org.scalatest.{Matchers, WordSpecLike}
|
||||||
|
import vinyldns.core.domain.batch._
|
||||||
|
import vinyldns.core.domain.record.RecordType
|
||||||
|
import vinyldns.core.domain.record.AData
|
||||||
|
import org.joda.time.DateTime
|
||||||
|
import vinyldns.core.TestMembershipData._
|
||||||
|
import java.nio.file.{Files, Path, Paths}
|
||||||
|
import cats.effect.{IO, Resource}
|
||||||
|
import scala.collection.JavaConverters._
|
||||||
|
import org.scalatest.BeforeAndAfterEach
|
||||||
|
import cats.implicits._
|
||||||
|
|
||||||
|
class EmailNotifierIntegrationSpec
|
||||||
|
extends MySqlApiIntegrationSpec
|
||||||
|
with MySqlIntegrationSpec
|
||||||
|
with Matchers
|
||||||
|
with WordSpecLike
|
||||||
|
with BeforeAndAfterEach {
|
||||||
|
|
||||||
|
import vinyldns.api.domain.DomainValidations._
|
||||||
|
|
||||||
|
val emailConfig: Config = ConfigFactory.load().getConfig("vinyldns.email.settings")
|
||||||
|
|
||||||
|
val targetDirectory = Paths.get("../../target")
|
||||||
|
|
||||||
|
override def beforeEach: Unit =
|
||||||
|
deleteEmailFiles(targetDirectory).unsafeRunSync()
|
||||||
|
|
||||||
|
override def afterEach: Unit =
|
||||||
|
deleteEmailFiles(targetDirectory).unsafeRunSync()
|
||||||
|
|
||||||
|
"Email Notifier" should {
|
||||||
|
|
||||||
|
"send an email" in {
|
||||||
|
val batchChange = BatchChange(
|
||||||
|
okUser.id,
|
||||||
|
okUser.userName,
|
||||||
|
None,
|
||||||
|
DateTime.now,
|
||||||
|
List(
|
||||||
|
SingleAddChange(
|
||||||
|
"some-zone-id",
|
||||||
|
"zone-name",
|
||||||
|
"record-name",
|
||||||
|
"a" * HOST_MAX_LENGTH,
|
||||||
|
RecordType.A,
|
||||||
|
300,
|
||||||
|
AData("1.1.1.1"),
|
||||||
|
SingleChangeStatus.Complete,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None)),
|
||||||
|
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||||
|
)
|
||||||
|
|
||||||
|
val program = for {
|
||||||
|
_ <- userRepository.save(okUser)
|
||||||
|
notifier <- new EmailNotifierProvider()
|
||||||
|
.load(NotifierConfig("", emailConfig), userRepository)
|
||||||
|
_ <- notifier.notify(Notification(batchChange))
|
||||||
|
emailFiles <- retrieveEmailFiles(targetDirectory)
|
||||||
|
} yield emailFiles
|
||||||
|
|
||||||
|
val files = program.unsafeRunSync()
|
||||||
|
|
||||||
|
files.length should be(1)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
def deleteEmailFiles(path: Path): IO[Unit] =
|
||||||
|
for {
|
||||||
|
files <- retrieveEmailFiles(path)
|
||||||
|
_ <- files.traverse { file =>
|
||||||
|
IO(Files.delete(file))
|
||||||
|
}
|
||||||
|
} yield ()
|
||||||
|
|
||||||
|
def retrieveEmailFiles(path: Path): IO[List[Path]] =
|
||||||
|
Resource.fromAutoCloseable(IO(Files.newDirectoryStream(path, "*.eml"))).use { s =>
|
||||||
|
IO {
|
||||||
|
s.iterator.asScala.toList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -91,6 +91,13 @@ vinyldns {
|
|||||||
|
|
||||||
notifiers = []
|
notifiers = []
|
||||||
|
|
||||||
|
email = {
|
||||||
|
class-name = "vinyldns.api.notifier.email.EmailNotifierProvider"
|
||||||
|
settings = {
|
||||||
|
from = "VinylDNS <do-not-reply@vinyldns.io>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
defaultZoneConnection {
|
defaultZoneConnection {
|
||||||
name = "vinyldns."
|
name = "vinyldns."
|
||||||
keyName = "vinyldns."
|
keyName = "vinyldns."
|
||||||
|
@ -0,0 +1,144 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 Comcast Cable Communications Management, LLC
|
||||||
|
*
|
||||||
|
* Licensed 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 vinyldns.api.notifier.email
|
||||||
|
|
||||||
|
import vinyldns.core.notifier.{Notification, Notifier}
|
||||||
|
import cats.effect.IO
|
||||||
|
import vinyldns.core.domain.batch.BatchChange
|
||||||
|
import vinyldns.core.domain.membership.UserRepository
|
||||||
|
import vinyldns.core.domain.membership.User
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import javax.mail.internet.{InternetAddress, MimeMessage}
|
||||||
|
import javax.mail.{Address, Message, Session}
|
||||||
|
import scala.util.Try
|
||||||
|
import vinyldns.core.domain.batch.SingleChange
|
||||||
|
import vinyldns.core.domain.batch.SingleAddChange
|
||||||
|
import vinyldns.core.domain.batch.SingleDeleteChange
|
||||||
|
import vinyldns.core.domain.record.AData
|
||||||
|
import vinyldns.core.domain.record.AAAAData
|
||||||
|
import vinyldns.core.domain.record.CNAMEData
|
||||||
|
import vinyldns.core.domain.record.MXData
|
||||||
|
import vinyldns.core.domain.record.TXTData
|
||||||
|
import vinyldns.core.domain.record.PTRData
|
||||||
|
import vinyldns.core.domain.record.RecordData
|
||||||
|
import org.joda.time.format.DateTimeFormat
|
||||||
|
import vinyldns.core.domain.batch.BatchChangeStatus._
|
||||||
|
import vinyldns.core.domain.batch.BatchChangeApprovalStatus._
|
||||||
|
|
||||||
|
class EmailNotifier(config: EmailNotifierConfig, session: Session, userRepository: UserRepository)
|
||||||
|
extends Notifier {
|
||||||
|
|
||||||
|
private val logger = LoggerFactory.getLogger("EmailNotifier")
|
||||||
|
|
||||||
|
def notify(notification: Notification[_]): IO[Unit] =
|
||||||
|
notification.change match {
|
||||||
|
case bc: BatchChange => sendBatchChangeNotification(bc)
|
||||||
|
case _ => IO.unit
|
||||||
|
}
|
||||||
|
|
||||||
|
def send(addresses: Address*)(buildMessage: Message => Message): IO[Unit] = IO {
|
||||||
|
val message = new MimeMessage(session)
|
||||||
|
message.setRecipients(Message.RecipientType.TO, addresses.toArray)
|
||||||
|
message.setFrom(config.from)
|
||||||
|
buildMessage(message)
|
||||||
|
message.saveChanges()
|
||||||
|
val transport = session.getTransport("smtp")
|
||||||
|
transport.connect()
|
||||||
|
transport.sendMessage(message, message.getAllRecipients())
|
||||||
|
transport.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
def sendBatchChangeNotification(bc: BatchChange): IO[Unit] =
|
||||||
|
userRepository.getUser(bc.userId).flatMap {
|
||||||
|
case Some(UserWithEmail(email)) =>
|
||||||
|
send(email) { message =>
|
||||||
|
message.setSubject(s"VinylDNS Batch change ${bc.id} results")
|
||||||
|
message.setContent(formatBatchChange(bc), "text/html")
|
||||||
|
message
|
||||||
|
}
|
||||||
|
case Some(user: User) if user.email.isDefined =>
|
||||||
|
IO {
|
||||||
|
logger.warn(
|
||||||
|
s"Unable to properly parse email for ${user.id}: ${user.email.getOrElse("<none>")}")
|
||||||
|
}
|
||||||
|
case None => IO { logger.warn(s"Unable to find user: ${bc.userId}") }
|
||||||
|
case _ => IO.unit
|
||||||
|
}
|
||||||
|
|
||||||
|
def formatBatchChange(bc: BatchChange): String =
|
||||||
|
s"""<h1>Batch Change Results</h1>
|
||||||
|
| <b>Submitter:</b> ${bc.userName} <br/>
|
||||||
|
| ${bc.comments.map(comments => s"<b>Description:</b> ${comments}</br>").getOrElse("")}
|
||||||
|
| <b>Created:</b> ${bc.createdTimestamp.toString(DateTimeFormat.fullDateTime)} <br/>
|
||||||
|
| <b>Id:</b> ${bc.id}<br/>
|
||||||
|
| <b>Status:</b> ${formatStatus(bc.approvalStatus, bc.status)}<br/>
|
||||||
|
| <table border = "1">
|
||||||
|
| <tr><th>#</th><th>Change Type</th><th>Record Type</th><th>Input Name</th>
|
||||||
|
| <th>TTL</th><th>Record Data</th><th>Status</th><th>Message</th></tr>
|
||||||
|
| ${bc.changes.zipWithIndex.map((formatSingleChange _).tupled).mkString("\n")}
|
||||||
|
| </table>
|
||||||
|
""".stripMargin
|
||||||
|
|
||||||
|
def formatStatus(approval: BatchChangeApprovalStatus, status: BatchChangeStatus): String =
|
||||||
|
(approval, status) match {
|
||||||
|
case (ManuallyRejected, _) => "Rejected"
|
||||||
|
case (PendingApproval, _) => "Pending Approval"
|
||||||
|
case (_, PartialFailure) => "Partially Failed"
|
||||||
|
case (_, status) => status.toString
|
||||||
|
}
|
||||||
|
|
||||||
|
def formatSingleChange(sc: SingleChange, index: Int): String = sc match {
|
||||||
|
case SingleAddChange(
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
inputName,
|
||||||
|
typ,
|
||||||
|
ttl,
|
||||||
|
recordData,
|
||||||
|
status,
|
||||||
|
systemMessage,
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
_) =>
|
||||||
|
s"""<tr><td>${index + 1}</td><td>Add</td><td>$typ</td><td>$inputName</td>
|
||||||
|
| <td>$ttl</td><td>${formatRecordData(recordData)}</td><td>$status</td>
|
||||||
|
| <td>${systemMessage.getOrElse("")}</td></tr>"""
|
||||||
|
case SingleDeleteChange(_, _, _, inputName, typ, status, systemMessage, _, _, _) =>
|
||||||
|
s"""<tr><td>${index + 1}</td><td>Delete</td><td>$typ</td><td>$inputName</td>
|
||||||
|
| <td></td><td></td><td>$status</td><td>${systemMessage.getOrElse("")}</td></tr>"""
|
||||||
|
}
|
||||||
|
|
||||||
|
def formatRecordData(rd: RecordData): String = rd match {
|
||||||
|
case AData(address) => address
|
||||||
|
case AAAAData(address) => address
|
||||||
|
case CNAMEData(cname) => cname
|
||||||
|
case MXData(preference, exchange) => s"Preference: $preference Exchange: $exchange"
|
||||||
|
case PTRData(name) => name
|
||||||
|
case TXTData(text) => text
|
||||||
|
case _ => rd.toString
|
||||||
|
}
|
||||||
|
|
||||||
|
object UserWithEmail {
|
||||||
|
def unapply(user: User): Option[Address] =
|
||||||
|
for {
|
||||||
|
email <- user.email
|
||||||
|
address <- Try(new InternetAddress(email)).toOption
|
||||||
|
} yield address
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 Comcast Cable Communications Management, LLC
|
||||||
|
*
|
||||||
|
* Licensed 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 vinyldns.api.notifier.email
|
||||||
|
|
||||||
|
import scala.collection.JavaConverters._
|
||||||
|
import javax.mail.Address
|
||||||
|
import javax.mail.internet.InternetAddress
|
||||||
|
import pureconfig.ConfigReader
|
||||||
|
import scala.util.Try
|
||||||
|
import pureconfig.error.CannotConvert
|
||||||
|
import java.util.Properties
|
||||||
|
import com.typesafe.config.{ConfigObject, ConfigValue}
|
||||||
|
import com.typesafe.config.ConfigValueType
|
||||||
|
|
||||||
|
object EmailNotifierConfig {
|
||||||
|
|
||||||
|
implicit val smtpPropertiesReader: ConfigReader[Properties] = {
|
||||||
|
ConfigReader[ConfigObject].map { config =>
|
||||||
|
val props = new Properties()
|
||||||
|
|
||||||
|
def convertToProperties(baseKey: String, config: ConfigObject): Unit =
|
||||||
|
config.keySet().asScala.foreach {
|
||||||
|
case key =>
|
||||||
|
config.get(key) match {
|
||||||
|
case value: ConfigObject =>
|
||||||
|
convertToProperties(s"${baseKey}.${key}", value)
|
||||||
|
case value: ConfigValue if value.valueType != ConfigValueType.NULL =>
|
||||||
|
props.put(s"${baseKey}.${key}", value.unwrapped())
|
||||||
|
case _ =>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
convertToProperties("mail.smtp", config)
|
||||||
|
props
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit val addressReader: ConfigReader[Address] = ConfigReader[String].emap { s =>
|
||||||
|
Try(new InternetAddress(s)).toEither.left.map { exc =>
|
||||||
|
CannotConvert(s, "InternetAddress", exc.getMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
case class EmailNotifierConfig(from: Address, smtp: Properties = new Properties())
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 Comcast Cable Communications Management, LLC
|
||||||
|
*
|
||||||
|
* Licensed 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 vinyldns.api.notifier.email
|
||||||
|
|
||||||
|
import vinyldns.core.notifier.{Notifier, NotifierConfig, NotifierProvider}
|
||||||
|
import vinyldns.core.domain.membership.UserRepository
|
||||||
|
import pureconfig.module.catseffect.loadConfigF
|
||||||
|
import cats.effect.IO
|
||||||
|
import javax.mail.Session
|
||||||
|
|
||||||
|
class EmailNotifierProvider extends NotifierProvider {
|
||||||
|
|
||||||
|
import EmailNotifierConfig._
|
||||||
|
|
||||||
|
def load(config: NotifierConfig, userRepository: UserRepository): IO[Notifier] =
|
||||||
|
for {
|
||||||
|
emailConfig <- loadConfigF[IO, EmailNotifierConfig](config.settings)
|
||||||
|
session <- createSession(emailConfig)
|
||||||
|
} yield new EmailNotifier(emailConfig, session, userRepository)
|
||||||
|
|
||||||
|
def createSession(config: EmailNotifierConfig): IO[Session] = IO {
|
||||||
|
Session.getInstance(config.smtp)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,237 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 Comcast Cable Communications Management, LLC
|
||||||
|
*
|
||||||
|
* Licensed 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 vinyldns.api.notifier.email
|
||||||
|
import org.scalatest.{BeforeAndAfterEach, Matchers, WordSpec}
|
||||||
|
import org.scalatest.mockito.MockitoSugar
|
||||||
|
import vinyldns.api.CatsHelpers
|
||||||
|
import javax.mail.{Provider, Session, Transport, URLName}
|
||||||
|
import java.util.Properties
|
||||||
|
import vinyldns.core.domain.membership.UserRepository
|
||||||
|
import vinyldns.core.notifier.Notification
|
||||||
|
import javax.mail.internet.InternetAddress
|
||||||
|
import org.mockito.Matchers.{eq => eqArg, _}
|
||||||
|
import org.mockito.Mockito._
|
||||||
|
import org.mockito.ArgumentCaptor
|
||||||
|
import cats.effect.IO
|
||||||
|
import javax.mail.{Address, Message}
|
||||||
|
import vinyldns.core.domain.membership.User
|
||||||
|
import vinyldns.core.domain.batch.BatchChange
|
||||||
|
import org.joda.time.DateTime
|
||||||
|
import vinyldns.core.domain.batch.BatchChangeApprovalStatus
|
||||||
|
import vinyldns.core.domain.batch.SingleChange
|
||||||
|
import vinyldns.core.domain.batch.SingleAddChange
|
||||||
|
import vinyldns.core.domain.batch.SingleDeleteChange
|
||||||
|
import vinyldns.core.domain.record.RecordType
|
||||||
|
import vinyldns.core.domain.record.AData
|
||||||
|
import _root_.vinyldns.core.domain.batch.SingleChangeStatus
|
||||||
|
import com.typesafe.config.Config
|
||||||
|
import com.typesafe.config.ConfigFactory
|
||||||
|
import scala.collection.JavaConverters._
|
||||||
|
import vinyldns.core.notifier.NotifierConfig
|
||||||
|
|
||||||
|
object MockTransport extends MockitoSugar {
|
||||||
|
val mockTransport = mock[Transport]
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockTransport(session: Session, urlname: URLName) extends Transport(session, urlname) {
|
||||||
|
import MockTransport._
|
||||||
|
|
||||||
|
override def connect(): Unit = mockTransport.connect()
|
||||||
|
|
||||||
|
override def close(): Unit = mockTransport.close()
|
||||||
|
|
||||||
|
def sendMessage(msg: Message, addresses: Array[Address]): Unit =
|
||||||
|
mockTransport.sendMessage(msg, addresses)
|
||||||
|
}
|
||||||
|
|
||||||
|
class EmailNotifierSpec
|
||||||
|
extends WordSpec
|
||||||
|
with Matchers
|
||||||
|
with MockitoSugar
|
||||||
|
with BeforeAndAfterEach
|
||||||
|
with CatsHelpers {
|
||||||
|
|
||||||
|
import MockTransport._
|
||||||
|
|
||||||
|
val mockUserRepository = mock[UserRepository]
|
||||||
|
val session = Session.getInstance(new Properties())
|
||||||
|
session.setProvider(
|
||||||
|
new Provider(
|
||||||
|
Provider.Type.TRANSPORT,
|
||||||
|
"smtp",
|
||||||
|
"vinyldns.api.notifier.email.MockTransport",
|
||||||
|
"vinyl",
|
||||||
|
"1.0"))
|
||||||
|
|
||||||
|
override protected def beforeEach(): Unit =
|
||||||
|
reset(mockUserRepository, mockTransport)
|
||||||
|
|
||||||
|
def batchChange(
|
||||||
|
description: Option[String] = None,
|
||||||
|
changes: List[SingleChange] = List.empty): BatchChange =
|
||||||
|
BatchChange(
|
||||||
|
"test",
|
||||||
|
"testUser",
|
||||||
|
description,
|
||||||
|
DateTime.now(),
|
||||||
|
changes,
|
||||||
|
None,
|
||||||
|
BatchChangeApprovalStatus.AutoApproved,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
"testBatch")
|
||||||
|
|
||||||
|
"Email Notifier" should {
|
||||||
|
"do nothing for unsupported Notifications" in {
|
||||||
|
val emailConfig: Config = ConfigFactory.parseMap(
|
||||||
|
Map[String, Any](
|
||||||
|
"from" -> "Testing <test@test.com>",
|
||||||
|
"smtp.host" -> "wouldfail.mail.com",
|
||||||
|
"smtp.auth.mechanisms" -> "PLAIN"
|
||||||
|
).asJava)
|
||||||
|
val notifier = new EmailNotifierProvider()
|
||||||
|
.load(NotifierConfig("", emailConfig), mockUserRepository)
|
||||||
|
.unsafeRunSync()
|
||||||
|
|
||||||
|
notifier.notify(new Notification("this won't be supported ever")) should be(IO.unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
"do nothing for user without email" in {
|
||||||
|
val notifier = new EmailNotifier(
|
||||||
|
EmailNotifierConfig(new InternetAddress("test@test.com"), new Properties()),
|
||||||
|
session,
|
||||||
|
mockUserRepository
|
||||||
|
)
|
||||||
|
|
||||||
|
doReturn(IO.pure(Some(User("testUser", "access", "secret"))))
|
||||||
|
.when(mockUserRepository)
|
||||||
|
.getUser("test")
|
||||||
|
|
||||||
|
notifier.notify(Notification(batchChange())).unsafeRunSync()
|
||||||
|
|
||||||
|
verify(mockUserRepository).getUser("test")
|
||||||
|
}
|
||||||
|
|
||||||
|
"do nothing when user not found" in {
|
||||||
|
val notifier = new EmailNotifier(
|
||||||
|
EmailNotifierConfig(new InternetAddress("test@test.com"), new Properties()),
|
||||||
|
session,
|
||||||
|
mockUserRepository
|
||||||
|
)
|
||||||
|
|
||||||
|
doReturn(IO.pure(None))
|
||||||
|
.when(mockUserRepository)
|
||||||
|
.getUser("test")
|
||||||
|
|
||||||
|
notifier.notify(Notification(batchChange())).unsafeRunSync()
|
||||||
|
|
||||||
|
verify(mockUserRepository).getUser("test")
|
||||||
|
}
|
||||||
|
|
||||||
|
"send an email to a user" in {
|
||||||
|
val fromAddress = new InternetAddress("test@test.com")
|
||||||
|
val notifier = new EmailNotifier(
|
||||||
|
EmailNotifierConfig(fromAddress, new Properties()),
|
||||||
|
session,
|
||||||
|
mockUserRepository
|
||||||
|
)
|
||||||
|
|
||||||
|
doReturn(
|
||||||
|
IO.pure(Some(User("testUser", "access", "secret", None, None, Some("testuser@test.com")))))
|
||||||
|
.when(mockUserRepository)
|
||||||
|
.getUser("test")
|
||||||
|
|
||||||
|
val expectedAddresses = Array[Address](new InternetAddress("testuser@test.com"))
|
||||||
|
val messageArgument = ArgumentCaptor.forClass(classOf[Message])
|
||||||
|
|
||||||
|
doNothing().when(mockTransport).connect()
|
||||||
|
doNothing()
|
||||||
|
.when(mockTransport)
|
||||||
|
.sendMessage(messageArgument.capture(), eqArg(expectedAddresses))
|
||||||
|
doNothing().when(mockTransport).close()
|
||||||
|
|
||||||
|
val description = "notes"
|
||||||
|
val singleChanges: List[SingleChange] = List(
|
||||||
|
SingleAddChange(
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"www.test.com",
|
||||||
|
RecordType.A,
|
||||||
|
200,
|
||||||
|
AData("1.2.3.4"),
|
||||||
|
SingleChangeStatus.Complete,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None),
|
||||||
|
SingleDeleteChange(
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"deleteme.test.com",
|
||||||
|
RecordType.A,
|
||||||
|
SingleChangeStatus.Failed,
|
||||||
|
Some("message for you"),
|
||||||
|
None,
|
||||||
|
None)
|
||||||
|
)
|
||||||
|
val change = batchChange(Some(description), singleChanges)
|
||||||
|
|
||||||
|
notifier.notify(Notification(change)).unsafeRunSync()
|
||||||
|
|
||||||
|
val message = messageArgument.getValue()
|
||||||
|
|
||||||
|
message.getFrom() should be(Array(fromAddress))
|
||||||
|
message.getContentType() should be("text/html; charset=us-ascii")
|
||||||
|
message.getAllRecipients() should be(expectedAddresses)
|
||||||
|
message.getSubject() should be("VinylDNS Batch change testBatch results")
|
||||||
|
val content = message.getContent().asInstanceOf[String]
|
||||||
|
|
||||||
|
content.contains(change.id) should be(true)
|
||||||
|
content.contains(description) should be(true)
|
||||||
|
|
||||||
|
val regex = raw"<tr>((.|\s)+?)<\/tr>".r
|
||||||
|
val rows = (for (m <- regex.findAllMatchIn(content)) yield m.group(0)).toList
|
||||||
|
|
||||||
|
def assertSingleChangeCaptured(row: String, sc: SingleChange): Unit = {
|
||||||
|
row.contains(sc.inputName) should be(true)
|
||||||
|
row.contains(sc.typ.toString) should be(true)
|
||||||
|
row.contains(sc.status.toString) should be(true)
|
||||||
|
sc.systemMessage.foreach(row.contains(_) should be(true))
|
||||||
|
sc match {
|
||||||
|
case ac: SingleAddChange =>
|
||||||
|
row.contains("Add") should be(true)
|
||||||
|
ac.recordData match {
|
||||||
|
case AData(address) => row.contains(address) should be(true)
|
||||||
|
case _ => row.contains(ac.recordData) should be(true)
|
||||||
|
}
|
||||||
|
row.contains(ac.ttl.toString) should be(true)
|
||||||
|
case _: SingleDeleteChange =>
|
||||||
|
row.contains("Delete") should be(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rows.tail.zip(singleChanges).foreach((assertSingleChangeCaptured _).tupled)
|
||||||
|
|
||||||
|
verify(mockUserRepository).getUser("test")
|
||||||
|
verify(mockTransport).sendMessage(any[Message], eqArg(expectedAddresses))
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -49,7 +49,9 @@ object Dependencies {
|
|||||||
"com.47deg" %% "github4s" % "0.18.6",
|
"com.47deg" %% "github4s" % "0.18.6",
|
||||||
"com.comcast" %% "ip4s-core" % ip4sV,
|
"com.comcast" %% "ip4s-core" % ip4sV,
|
||||||
"com.comcast" %% "ip4s-cats" % ip4sV,
|
"com.comcast" %% "ip4s-cats" % ip4sV,
|
||||||
"com.iheart" %% "ficus" % "1.4.3"
|
"com.iheart" %% "ficus" % "1.4.3",
|
||||||
|
"com.sun.mail" % "javax.mail" % "1.6.2",
|
||||||
|
"javax.mail" % "javax.mail-api" % "1.6.2"
|
||||||
)
|
)
|
||||||
|
|
||||||
lazy val coreDependencies = Seq(
|
lazy val coreDependencies = Seq(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user