Skip to content

Commit

Permalink
Improve unit test coverage of the email notification rendering.
Browse files Browse the repository at this point in the history
  • Loading branch information
yruslan committed Nov 1, 2023
1 parent 0d3b3ce commit 2b3bf99
Show file tree
Hide file tree
Showing 4 changed files with 235 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ class PipelineNotificationBuilderHtml(implicit conf: Config) extends PipelineNot
builder.renderBody
}

private def renderHeader(builder: MessageBuilder): MessageBuilder = {
private[core] def renderHeader(builder: MessageBuilder): MessageBuilder = {
val introParagraph = ParagraphBuilder()

if (isDryRun)
Expand Down Expand Up @@ -210,21 +210,21 @@ class PipelineNotificationBuilderHtml(implicit conf: Config) extends PipelineNot
builder
}

private def getSuccessFlags: (Boolean, Boolean) = {
private[core] def getSuccessFlags: (Boolean, Boolean) = {
val hasNotificationFailures = completedTasks.exists(t => t.notificationTargetErrors.nonEmpty)
val someTasksSucceeded = completedTasks.exists(_.runStatus.isInstanceOf[Succeeded]) && appException.isEmpty
val someTasksFailed = completedTasks.exists(t => t.runStatus.isFailure) || hasNotificationFailures || appException.nonEmpty
(someTasksSucceeded, someTasksFailed)
}

private def getZoneId: ZoneId = {
private[core] def getZoneId: ZoneId = {
ConfigUtils.getOptionString(conf, TIMEZONE) match {
case Some(tz) => ZoneId.of(tz)
case None => ZoneId.systemDefault()
}
}

private def renderJobException(builder: MessageBuilder, taskResult: TaskResult, ex: Throwable): MessageBuilder = {
private[core] def renderJobException(builder: MessageBuilder, taskResult: TaskResult, ex: Throwable): MessageBuilder = {
val paragraphBuilder = ParagraphBuilder()
.withText("Job ", Style.Exception)
.withText(taskResult.job.name, Style.Error)
Expand All @@ -245,7 +245,7 @@ class PipelineNotificationBuilderHtml(implicit conf: Config) extends PipelineNot
renderException(builder, ex)
}

private def renderException(builder: MessageBuilder, ex: Throwable): MessageBuilder = {
private[core] def renderException(builder: MessageBuilder, ex: Throwable): MessageBuilder = {
val text = ex match {
case CmdFailedException(msg, logLines) =>
if (logLines.isEmpty) {
Expand Down Expand Up @@ -287,7 +287,7 @@ class PipelineNotificationBuilderHtml(implicit conf: Config) extends PipelineNot
builder
}

private def renderTaskTable(builder: MessageBuilder, tasks: Seq[TaskResult]): MessageBuilder = {
private[core] def renderTaskTable(builder: MessageBuilder, tasks: Seq[TaskResult]): MessageBuilder = {
val outputRecordsKnown = tasks.exists(t => t.runStatus match {
case _: Succeeded => true
case _ => false
Expand Down Expand Up @@ -363,7 +363,7 @@ class PipelineNotificationBuilderHtml(implicit conf: Config) extends PipelineNot
builder.withTable(tableBuilder)
}

def renderNotificationTargetErrors(builder: MessageBuilderHtml, notificationTargetErrors: ListBuffer[NotificationFailure]): MessageBuilder = {
private[core] def renderNotificationTargetErrors(builder: MessageBuilderHtml, notificationTargetErrors: ListBuffer[NotificationFailure]): MessageBuilder = {
val tableBuilder = new TableBuilderHtml

val tableHeaders = new ListBuffer[TableHeader]
Expand Down Expand Up @@ -392,7 +392,7 @@ class PipelineNotificationBuilderHtml(implicit conf: Config) extends PipelineNot
builder.withTable(tableBuilder)
}

private def renderFilesRead(builder: MessageBuilder, task: TaskResult, runStatus: RunStatus.Succeeded): MessageBuilder = {
private[core] def renderFilesRead(builder: MessageBuilder, task: TaskResult, runStatus: RunStatus.Succeeded): MessageBuilder = {
val tableBuilder = new TableBuilderHtml

val tableHeaders = new ListBuffer[TableHeader]
Expand All @@ -414,7 +414,7 @@ class PipelineNotificationBuilderHtml(implicit conf: Config) extends PipelineNot
builder.withTable(tableBuilder)
}

private def getThroughputRps(task: TaskResult): TextElement = {
private[core] def getThroughputRps(task: TaskResult): TextElement = {
val recordCount = task.runStatus match {
case s: Succeeded => s.recordCount
case _ => 0
Expand All @@ -437,7 +437,7 @@ class PipelineNotificationBuilderHtml(implicit conf: Config) extends PipelineNot
}
}

private def getRecordCountText(task: TaskResult): String = {
private[core] def getRecordCountText(task: TaskResult): String = {
def renderDifference(numRecords: Long, numRecordsOld: Option[Long]): String = {
numRecordsOld match {
case Some(old) if old > 0 =>
Expand All @@ -460,14 +460,14 @@ class PipelineNotificationBuilderHtml(implicit conf: Config) extends PipelineNot
}
}

private def getElapsedTime(task: TaskResult): String = {
private[core] def getElapsedTime(task: TaskResult): String = {
task.runInfo match {
case Some(runInfo) => TimeUtils.prettyPrintElapsedTimeShort((runInfo.finished.getEpochSecond - runInfo.started.getEpochSecond) * 1000L)
case _ => ""
}
}

private def getOutputSize(task: TaskResult): String = {
private[core] def getOutputSize(task: TaskResult): String = {
task.runStatus match {
case s: Succeeded =>
s.sizeBytes match {
Expand All @@ -478,7 +478,7 @@ class PipelineNotificationBuilderHtml(implicit conf: Config) extends PipelineNot
}
}

private def getFailureReason(task: TaskResult): String = {
private[core] def getFailureReason(task: TaskResult): String = {
task.runStatus.getReason() match {
case Some(reason) => reason
case None =>
Expand All @@ -491,14 +491,14 @@ class PipelineNotificationBuilderHtml(implicit conf: Config) extends PipelineNot
}
}

private def getFinishTime(task: TaskResult): String = {
private[core] def getFinishTime(task: TaskResult): String = {
task.runInfo match {
case Some(runInfo) => ZonedDateTime.ofInstant(runInfo.finished, zoneId).format(timestampFmt)
case None => ""
}
}

private def getStatus(task: TaskResult): TextElement = {
private[core] def getStatus(task: TaskResult): TextElement = {
val successStyle = if (task.dependencyWarnings.nonEmpty) Style.Warning else Style.Success

task.runStatus match {
Expand All @@ -514,7 +514,7 @@ class PipelineNotificationBuilderHtml(implicit conf: Config) extends PipelineNot
}
}

private def getSuccessTextElement(status: RunStatus.Succeeded, hasDependencyWarnings: Boolean): TextElement = {
private[core] def getSuccessTextElement(status: RunStatus.Succeeded, hasDependencyWarnings: Boolean): TextElement = {
val successStyle = if (hasDependencyWarnings) Style.Warning else Style.Success

val style = if (status.warnings.nonEmpty)
Expand All @@ -539,7 +539,7 @@ class PipelineNotificationBuilderHtml(implicit conf: Config) extends PipelineNot
}
}

private def renderSchemaDifference(builder: MessageBuilder, schemaDifferences: Seq[SchemaDifference]): MessageBuilder = {
private[core] def renderSchemaDifference(builder: MessageBuilder, schemaDifferences: Seq[SchemaDifference]): MessageBuilder = {
if (schemaDifferences.isEmpty) {
return builder
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ package za.co.absa.pramen.core.mocks
import za.co.absa.pramen.core.mocks.job.JobSpy
import za.co.absa.pramen.core.notify.pipeline.SchemaDifference
import za.co.absa.pramen.core.pipeline.{DependencyWarning, Job, TaskRunReason}
import za.co.absa.pramen.core.runner.task.{RunInfo, RunStatus, TaskResult}
import za.co.absa.pramen.core.runner.task.{NotificationFailure, RunInfo, RunStatus, TaskResult}

import java.time.{Instant, LocalDate}

Expand All @@ -30,15 +30,16 @@ object TaskResultFactory {
applicationId: String = "app_123",
isTransient: Boolean = false,
schemaDifferences: Seq[SchemaDifference] = Nil,
dependencyWarnings: Seq[DependencyWarning] = Nil): TaskResult = {
dependencyWarnings: Seq[DependencyWarning] = Nil,
notificationTargetErrors: Seq[NotificationFailure] = Nil): TaskResult = {
TaskResult(job,
runStatus,
runInfo,
applicationId,
isTransient,
schemaDifferences,
dependencyWarnings,
Nil)
notificationTargetErrors)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2022 ABSA Group Limited
*
* 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 za.co.absa.pramen.core.mocks

import za.co.absa.pramen.core.pipeline.TaskRunReason
import za.co.absa.pramen.core.runner.task.RunStatus

object TestPrototypes {

val runStatusSuccess: RunStatus = RunStatus.Succeeded(Some(100), 200, Some(1000), TaskRunReason.New, Seq.empty, Seq.empty, Seq.empty, Seq.empty)

val runStatusWarning: RunStatus = RunStatus.Succeeded(
Some(100), 200, Some(1000), TaskRunReason.New, Seq("file1.txt", "file1.ctl"),
Seq("file1.csv", "file2.csv"), Seq("`db`.`table1`"), Seq("Test warning")
)

val runStatusFailure: RunStatus = RunStatus.Failed(new RuntimeException("Test exception"))
}
Loading

0 comments on commit 2b3bf99

Please sign in to comment.