Skip to content

Commit

Permalink
Add tests for DashboardReporter
Browse files Browse the repository at this point in the history
  • Loading branch information
hugo-vrijswijk committed Nov 30, 2019
1 parent c2a5588 commit f9c4565
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 39 deletions.
8 changes: 2 additions & 6 deletions core/src/main/scala/stryker4s/env/EnvProvider.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
package stryker4s.env

trait Environment {
def getEnvVariable(key: String): Option[String]
}

object SystemEnvironment extends Environment {
override def getEnvVariable(key: String): Option[String] = sys.env.get(key)
object Environment {
type Environment = Map[String, String]
}
11 changes: 6 additions & 5 deletions core/src/main/scala/stryker4s/report/DashboardReporter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ class DashboardReporter(dashboardConfigProvider: DashboardConfigProvider)(
def buildRequest(dashConfig: DashboardConfig, report: MutationTestReport, metrics: MetricsResult) = {
import io.circe.{Decoder, Encoder}
implicit val decoder: Decoder[DashboardPutResult] = Decoder.forProduct1("href")(DashboardPutResult.apply)
val uri =
uri"${dashConfig.baseUrl}/api/reports/${dashConfig.project}/${dashConfig.version}?module=${dashConfig.module}"
// Separate so any slashes won't be escaped in project or version
val baseUrl = s"${dashConfig.baseUrl}/api/reports/${dashConfig.project}/${dashConfig.version}"
val uri = uri"$baseUrl?module=${dashConfig.module}"
val request = basicRequest
.header("X-Api-Key", dashConfig.apiKey)
.response(asJson[DashboardPutResult])
.contentType(MediaType.ApplicationJson)
.response(asJson[DashboardPutResult])
.put(uri)
dashConfig.reportType match {
case Full =>
Expand All @@ -52,15 +53,15 @@ class DashboardReporter(dashboardConfigProvider: DashboardConfigProvider)(
response.code match {
case StatusCode.Unauthorized =>
error(
s"Error HTTP PUT $errorBody. Unauthorized. Did you provide the correct api key in the 'STRYKER_DASHBOARD_API_KEY' environment variable?"
s"Error HTTP PUT '$errorBody'. Status code 401 Unauthorized. Did you provide the correct api key in the 'STRYKER_DASHBOARD_API_KEY' environment variable?"
)
case statusCode =>
error(
s"Failed to PUT report to dashboard. Response status code: ${statusCode.code}. Response body: '${errorBody}'"
)
}
case Left(DeserializationError(original, error)) =>
warn(s"Dashboard report was sent successfully, but could not decode the response $original:", error)
warn(s"Dashboard report was sent successfully, but could not decode the response: '$original'. Error:", error)
case Right(DashboardPutResult(href)) =>
info(s"Sent report to dashboard. Available at $href")
}
Expand Down
3 changes: 1 addition & 2 deletions core/src/main/scala/stryker4s/report/Reporter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import stryker4s.model.{Mutant, MutantRunResult}
import stryker4s.report.dashboard.DashboardConfigProvider
import scala.util.{Failure, Try}
import sttp.client.HttpURLConnectionBackend
import stryker4s.env.SystemEnvironment

class Reporter(implicit config: Config) extends FinishedRunReporter with ProgressReporter with Logging {
lazy val reporters: Seq[MutationRunReporter] = config.reporters map {
Expand All @@ -17,7 +16,7 @@ class Reporter(implicit config: Config) extends FinishedRunReporter with Progres
case Json => new JsonReporter(DiskFileIO)
case Dashboard =>
implicit val backend = HttpURLConnectionBackend()
new DashboardReporter(new DashboardConfigProvider(SystemEnvironment))
new DashboardReporter(new DashboardConfigProvider(sys.env))
}

private[this] val progressReporters = reporters collect { case r: ProgressReporter => r }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package stryker4s.report.dashboard
import stryker4s.report.model.DashboardConfig
import stryker4s.config.Config
import stryker4s.report.dashboard.Providers._
import stryker4s.env.Environment
import stryker4s.env.Environment.Environment

class DashboardConfigProvider(env: Environment)(implicit config: Config) {
def resolveConfig(): Either[String, DashboardConfig] =
Expand All @@ -22,10 +22,11 @@ class DashboardConfigProvider(env: Environment)(implicit config: Config) {
module = module
)

private val apiKeyName = "STRYKER_DASHBOARD_API_KEY"
private def resolveapiKey() =
env
.getEnvVariable("STRYKER_DASHBOARD_API_KEY")
.toRight("STRYKER_DASHBOARD_API_KEY")
.get(apiKeyName)
.toRight(apiKeyName)

private def resolveproject() =
config.dashboard.project
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package stryker4s.report.dashboard
import grizzled.slf4j.Logging
import stryker4s.env.Environment
import stryker4s.env.Environment.Environment

object Providers extends Logging {
def determineCiProvider(env: Environment): Option[CiProvider] =
if (env.getEnvVariable("TRAVIS").isDefined) {
if (env.get("TRAVIS").isDefined) {
Some(new TravisProvider(env))
} else if (env.getEnvVariable("CIRCLECI").isDefined) {
} else if (env.get("CIRCLECI").isDefined) {
Some(new CircleProvider(env))
} else {
None
Expand All @@ -18,7 +18,7 @@ object Providers extends Logging {
}

private def readEnvironmentVariable(name: String, env: Environment): Option[String] =
env.getEnvVariable(name).filter(_.nonEmpty)
env.get(name).filter(_.nonEmpty)

class TravisProvider(env: Environment) extends CiProvider {
override def determineProject(): Option[String] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ trait ProcessRunner extends Logging {
}

object ProcessRunner {
private val isWindows: Boolean = sys.props("os.name").toLowerCase.contains("windows")
private def isWindows: Boolean = sys.props("os.name").toLowerCase.contains("windows")

def apply(): ProcessRunner = {
if (isWindows) new WindowsProcessRunner
Expand Down
135 changes: 121 additions & 14 deletions core/src/test/scala/stryker4s/report/DashboardReporterTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,37 +9,144 @@ import stryker4s.config.Full
import mutationtesting.MutationTestReport
import mutationtesting.Metrics
import sttp.client._
import sttp.model.{Header, HeaderNames, MediaType, Method}
import sttp.model.Header
import sttp.model.Method
import stryker4s.report.model.DashboardPutResult
import sttp.model.MediaType
import stryker4s.config.MutationScoreOnly
import sttp.model.StatusCode

class DashboardReporterTest extends Stryker4sSuite with MockitoSuite with LogMatchers {
describe("buildRequest") {
it("should compose the url") {
implicit val backend = testBackend
it("should compose the request") {
implicit val backend = backendStub
val mockDashConfig = mock[DashboardConfigProvider]
val sut = new DashboardReporter(mockDashConfig)
val dashConfig = DashboardConfig(
"apiKeyHere",
reportType = Full,
baseUrl = "https://baseurl.com",
project = "project/foo",
version = "version/bar",
module = None
)
val dashConfig = baseDashConfig
val (report, metrics) = baseResults

val request = sut.buildRequest(dashConfig, report, metrics)
request.uri shouldBe uri"https://baseurl.com/api/reports/project/foo/version/bar"
val jsonBody = {
import mutationtesting.MutationReportEncoder._
import io.circe.syntax._
report.asJson.noSpaces
}
request.body shouldBe StringBody(jsonBody, "utf-8", Some(MediaType.ApplicationJson))
request.method shouldBe Method.PUT
request.headers should contain(new Header("X-Api-Key", "apiKeyHere"))
request.headers should contain(new Header(HeaderNames.ContentType, MediaType.ApplicationJson.toString()))
request.headers should contain allOf (
new Header("X-Api-Key", "apiKeyHere"),
new Header("Content-Type", "application/json")
)
}

it("should make a score-only request when score-only is configured") {
implicit val backend = backendStub
val mockDashConfig = mock[DashboardConfigProvider]
val sut = new DashboardReporter(mockDashConfig)
val dashConfig = baseDashConfig.copy(reportType = MutationScoreOnly)
val (report, metrics) = baseResults

val request = sut.buildRequest(dashConfig, report, metrics)
request.uri shouldBe uri"https://baseurl.com/api/reports/project/foo/version/bar"
val jsonBody = """{"mutationScore":0.0}"""
request.body shouldBe StringBody(jsonBody, "utf-8", Some(MediaType.ApplicationJson))
}

it("should add the module if it is present") {
implicit val backend = backendStub
val mockDashConfig = mock[DashboardConfigProvider]
val sut = new DashboardReporter(mockDashConfig)
val dashConfig = baseDashConfig.copy(module = Some("myModule"))
val (report, metrics) = baseResults

val request = sut.buildRequest(dashConfig, report, metrics)

request.uri shouldBe uri"https://baseurl.com/api/reports/project/foo/version/bar?module=myModule"
}
}

def testBackend = SttpBackendStub.synchronous
describe("reportRunFinished") {
it("should send the request") {
implicit val backend = backendStub.whenAnyRequest
.thenRespond(Right(DashboardPutResult("https://hrefHere.com")))
val mockDashConfig = mock[DashboardConfigProvider]
when(mockDashConfig.resolveConfig()).thenReturn(Right(baseDashConfig))
val sut = new DashboardReporter(mockDashConfig)
val (report, metrics) = baseResults

sut.reportRunFinished(report, metrics)

"Sent report to dashboard. Available at https://hrefHere.com" shouldBe loggedAsInfo
}

it("log when not being able to resolve dashboard config") {
implicit val backend = backendStub
val mockDashConfig = mock[DashboardConfigProvider]
when(mockDashConfig.resolveConfig()).thenReturn(Left("fooConfigKey"))
val sut = new DashboardReporter(mockDashConfig)
val (report, metrics) = baseResults

sut.reportRunFinished(report, metrics)

"Could not resolve dashboard configuration key 'fooConfigKey', not sending report" shouldBe loggedAsWarning
}

it("should log when a response can't be parsed to a href") {
implicit val backend = backendStub.whenAnyRequest.thenRespond("some other response")
val mockDashConfig = mock[DashboardConfigProvider]
when(mockDashConfig.resolveConfig()).thenReturn(Right(baseDashConfig))
val sut = new DashboardReporter(mockDashConfig)
val (report, metrics) = baseResults

sut.reportRunFinished(report, metrics)

"Dashboard report was sent successfully, but could not decode the response: 'some other response'. Error:" shouldBe loggedAsWarning
}

it("should log when a 401 is returned by the API") {
implicit val backend = backendStub.whenAnyRequest
.thenRespond(Response(Left(HttpError("auth required")), StatusCode.Unauthorized))
val mockDashConfig = mock[DashboardConfigProvider]
when(mockDashConfig.resolveConfig()).thenReturn(Right(baseDashConfig))
val sut = new DashboardReporter(mockDashConfig)
val (report, metrics) = baseResults

sut.reportRunFinished(report, metrics)

"Error HTTP PUT 'auth required'. Status code 401 Unauthorized. Did you provide the correct api key in the 'STRYKER_DASHBOARD_API_KEY' environment variable?" shouldBe loggedAsError
}

it("should log when a error code is returned by the API") {
implicit val backend =
backendStub.whenAnyRequest.thenRespond(
Response(Left(HttpError("internal error")), StatusCode.InternalServerError)
)
val mockDashConfig = mock[DashboardConfigProvider]
when(mockDashConfig.resolveConfig()).thenReturn(Right(baseDashConfig))
val sut = new DashboardReporter(mockDashConfig)
val (report, metrics) = baseResults

sut.reportRunFinished(report, metrics)

"Failed to PUT report to dashboard. Response status code: 500. Response body: 'internal error'" shouldBe loggedAsError
}
}

def backendStub = SttpBackendStub.synchronous

def baseResults = {
val report = MutationTestReport(thresholds = mutationtesting.Thresholds(80, 60), files = Map.empty)
val metrics = Metrics.calculateMetrics(report)
(report, metrics)
}

def baseDashConfig = DashboardConfig(
apiKey = "apiKeyHere",
reportType = Full,
baseUrl = "https://baseurl.com",
project = "project/foo",
version = "version/bar",
module = None
)
}
5 changes: 2 additions & 3 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ object Dependencies {
val circe = "0.12.3"
val mutationTestingElements = "1.2.1"
val mutationTestingMetrics = "1.2.0"
val sttp = "2.0.0-RC2"
val sttp = "2.0.0-RC3"
}

object test {
Expand All @@ -32,8 +32,7 @@ object Dependencies {
val grizzledSlf4j = "org.clapper" %% "grizzled-slf4j" % versions.grizzledSlf4j
val catsCore = "org.typelevel" %% "cats-core" % versions.cats
val circeCore = "io.circe" %% "circe-core" % versions.circe
val sttp = "com.softwaremill.sttp.client" %% "core" % versions.sttp
val sttpCirce = "com.softwaremill.sttp.client" %% "circe" % versions.sttp
val sttp = "com.softwaremill.sttp.client" %% "circe" % versions.sttp
val mutationTestingElements = "io.stryker-mutator" % "mutation-testing-elements" % versions.mutationTestingElements
val mutationTestingMetrics = "io.stryker-mutator" %% "mutation-testing-metrics-circe" % versions.mutationTestingMetrics
}
1 change: 0 additions & 1 deletion project/Settings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ object Settings {
Dependencies.log4jslf4jImpl % Test, // Logging tests need a slf4j implementation
Dependencies.circeCore,
Dependencies.sttp,
Dependencies.sttpCirce,
Dependencies.mutationTestingElements,
Dependencies.mutationTestingMetrics
)
Expand Down

0 comments on commit f9c4565

Please sign in to comment.