From cf8f9262f7bd22d7b0e1c25d8531b0361d8112af Mon Sep 17 00:00:00 2001 From: Francisco Duarte Date: Tue, 18 Feb 2020 09:41:13 +0000 Subject: [PATCH] feature: Add support for api tokens :breaking: (#153) Instead of using a project token, users can use an api token and specify username and project name clean: Move sys.exit to program edge - Change abstract method run to return int in ConfigurationParsingApp - Make Components receive Configuration instead of CommandConfiguration Co-authored-by: Lorenzo Gabriele --- .gitignore | 2 + README.md | 18 ++- build.sbt | 4 +- project/plugins.sbt | 2 +- .../com/codacy/CodacyCoverageReporter.scala | 49 ++++--- .../parser/ConfigurationParser.scala | 12 +- src/main/scala/com/codacy/di/Components.scala | 23 +-- .../model/configuration/Configuration.scala | 14 +- .../com/codacy/rules/ConfigurationRules.scala | 135 ++++++++++-------- .../scala/com/codacy/rules/ReportRules.scala | 29 +++- src/test/resources/dotcover-example.xml | 78 ++++++++++ src/test/resources/invalid-report.xml | 1 + .../codacy/rules/ConfigurationRulesSpec.scala | 106 +++++++++++--- .../com/codacy/rules/ReportRulesSpec.scala | 82 +++++++++-- 14 files changed, 427 insertions(+), 128 deletions(-) create mode 100644 src/test/resources/dotcover-example.xml create mode 100644 src/test/resources/invalid-report.xml diff --git a/.gitignore b/.gitignore index 83dcee6a..01017ec9 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ config .codacy.json codacy-coverage-reporter-linux-* codacy-coverage-reporter-darwin-* +classes/ +src/test/resources/codacy-coverage.json diff --git a/README.md b/README.md index df80a266..ec66ab38 100644 --- a/README.md +++ b/README.md @@ -17,12 +17,28 @@ The supported coverage formats can be found [here](https://github.com/codacy/cov 1. Setup the project API token. You can find the token in Project -> Settings -> Integrations -> Project API. -Then set it in your terminal, replacing %Project_Token% with your own token: +1. Set it in your terminal, replacing %Project_Token% with your own token: ``` export CODACY_PROJECT_TOKEN=%Project_Token% ``` +##### Alternative: using an API Token + +1. Setup the Account API token. You can find the token in Your account -> API tokens + +1. Set it in your terminal, replacing the %API_Token% with your own token: + +1. Set your project name in your terminal, replacing the %Project_Name% + +1. Set your username in your terminal, replacing the %Username% + +``` +export CODACY_API_TOKEN=%API_Token% +export CODACY_PROJECT_NAME=%Project_Name% +export CODACY_USERNAME=%Username% +``` + ### Using the script Additional requirements: diff --git a/build.sbt b/build.sbt index 5caf55fe..28113301 100644 --- a/build.sbt +++ b/build.sbt @@ -16,14 +16,14 @@ scalacOptions := Seq( // Runtime dependencies libraryDependencies ++= Seq( - "com.codacy" %% "coverage-parser" % "2.5.0", + "com.codacy" %% "coverage-parser" % "2.5.4", "com.github.alexarchambault" %% "case-app" % "1.2.0", logbackClassic, scalaLogging ) // Test dependencies -libraryDependencies ++= Seq(scalatest).map(_ % "test") +libraryDependencies ++= Seq(scalatest, mockitoScalaScalatest).map(_ % Test) mainClass in assembly := Some("com.codacy.CodacyCoverageReporter") assemblyMergeStrategy in assembly := { diff --git a/project/plugins.sbt b/project/plugins.sbt index cafb6f46..c632c7a7 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ resolvers := Seq(DefaultMavenRepository, Resolver.jcenterRepo, Resolver.sonatypeRepo("releases")) -addSbtPlugin("com.codacy" % "codacy-sbt-plugin" % "14.0.1") +addSbtPlugin("com.codacy" % "codacy-sbt-plugin" % "18.0.3") // Publish addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.9") diff --git a/src/main/scala/com/codacy/CodacyCoverageReporter.scala b/src/main/scala/com/codacy/CodacyCoverageReporter.scala index d1f89f8e..e2eb22c6 100644 --- a/src/main/scala/com/codacy/CodacyCoverageReporter.scala +++ b/src/main/scala/com/codacy/CodacyCoverageReporter.scala @@ -5,33 +5,44 @@ import com.codacy.di.Components import com.codacy.helpers.LoggerHelper import com.codacy.model.configuration.{FinalConfig, ReportConfig} import com.typesafe.scalalogging.StrictLogging +import com.codacy.rules.ConfigurationRules object CodacyCoverageReporter extends ConfigurationParsingApp with StrictLogging { - def run(commandConfig: CommandConfiguration): Unit = { - val components = new Components(commandConfig) + def run(commandConfig: CommandConfiguration): Int = { + val noAvailableTokens = commandConfig.baseConfig.projectToken.isEmpty && commandConfig.baseConfig.apiToken.isEmpty + if (commandConfig.baseConfig.skipValue && noAvailableTokens) { + logger.info("Skip reporting coverage") + 0 + } else { + val result: Either[String, String] = sendReport(commandConfig) + result match { + case Right(message) => + logger.info(message) + 0 + case Left(message) => + logger.error(message) + 1 + } + } + } - val validatedConfig = components.validatedConfig + private def sendReport(commandConfig: CommandConfiguration) = { + val configRules = new ConfigurationRules(commandConfig) - LoggerHelper.setLoggerLevel(logger, validatedConfig.baseConfig.debug) + configRules.validatedConfig.flatMap { validatedConfig => + val components = new Components(validatedConfig) + LoggerHelper.setLoggerLevel(logger, validatedConfig.baseConfig.debug) - logger.debug(validatedConfig.toString) + logger.debug(validatedConfig.toString) - val result = validatedConfig match { - case config: ReportConfig => - components.reportRules.codacyCoverage(config) + validatedConfig match { + case config: ReportConfig => + components.reportRules.codacyCoverage(config) - case config: FinalConfig => - components.reportRules.finalReport(config) + case config: FinalConfig => + components.reportRules.finalReport(config) + } } - - result.fold({ error => - logger.error(error) - sys.exit(1) - }, { successMessage => - logger.info(successMessage) - sys.exit(0) - }) } - } diff --git a/src/main/scala/com/codacy/configuration/parser/ConfigurationParser.scala b/src/main/scala/com/codacy/configuration/parser/ConfigurationParser.scala index 08a21dbd..dbca4496 100644 --- a/src/main/scala/com/codacy/configuration/parser/ConfigurationParser.scala +++ b/src/main/scala/com/codacy/configuration/parser/ConfigurationParser.scala @@ -10,10 +10,10 @@ import com.codacy.configuration.parser.ConfigArgumentParsers._ abstract class ConfigurationParsingApp extends CommandAppWithPreCommand[BaseCommand, CommandConfiguration] { override final def run(options: CommandConfiguration, remainingArgs: RemainingArgs): Unit = { - run(options) + sys.exit(run(options)) } - def run(config: CommandConfiguration): Unit + def run(config: CommandConfiguration): Int override def beforeCommand(options: BaseCommand, remainingArgs: Seq[String]): Unit = () } @@ -55,11 +55,17 @@ case class Report( case class BaseCommandConfig( @Name("t") @ValueDescription("your project API token") projectToken: Option[String], + @Name("a") @ValueDescription("your api token") + apiToken: Option[String], + @Name("u") @ValueDescription("your username") + username: Option[String], + @Name("p") @ValueDescription("project name") + projectName: Option[String], @ValueDescription("the base URL for the Codacy API") codacyApiBaseUrl: Option[String], @ValueDescription("your commitUUID") commitUUID: Option[String], - @Name("s") @ValueDescription("skip if project token isn't defined") + @Name("s") @ValueDescription("skip if token isn't defined") skip: Int @@ Counter = Tag.of(0), @Hidden debug: Int @@ Counter = Tag.of(0) diff --git a/src/main/scala/com/codacy/di/Components.scala b/src/main/scala/com/codacy/di/Components.scala index 6ea2a403..fecc3e17 100644 --- a/src/main/scala/com/codacy/di/Components.scala +++ b/src/main/scala/com/codacy/di/Components.scala @@ -2,20 +2,21 @@ package com.codacy.di import com.codacy.api.client.CodacyClient import com.codacy.api.service.CoverageServices -import com.codacy.configuration.parser.CommandConfiguration -import com.codacy.model.configuration.Configuration -import com.codacy.rules.{ConfigurationRules, ReportRules} +import com.codacy.model.configuration.{ApiTokenAuthenticationConfig, Configuration, ProjectTokenAuthenticationConfig} +import com.codacy.rules.ReportRules -class Components(private val cmdConfig: CommandConfiguration) { - lazy val validatedConfig: Configuration = configRules.validatedConfig +class Components(private val validatedConfig: Configuration) { + lazy val reportRules = new ReportRules(coverageServices) - lazy val configRules = new ConfigurationRules(cmdConfig) - lazy val reportRules = new ReportRules(validatedConfig, coverageServices) + lazy private val (projectToken, apiToken) = validatedConfig.baseConfig.authentication match { + case ProjectTokenAuthenticationConfig(projectToken) => + (Some(projectToken), None) + case ApiTokenAuthenticationConfig(apiToken, _, _) => + (None, Some(apiToken)) + } + + lazy val codacyClient = new CodacyClient(Some(validatedConfig.baseConfig.codacyApiBaseUrl), apiToken, projectToken) - lazy val codacyClient = new CodacyClient( - Some(validatedConfig.baseConfig.codacyApiBaseUrl), - projectToken = Some(validatedConfig.baseConfig.projectToken) - ) lazy val coverageServices = new CoverageServices(codacyClient) } diff --git a/src/main/scala/com/codacy/model/configuration/Configuration.scala b/src/main/scala/com/codacy/model/configuration/Configuration.scala index 612433c5..7f78e0bd 100644 --- a/src/main/scala/com/codacy/model/configuration/Configuration.scala +++ b/src/main/scala/com/codacy/model/configuration/Configuration.scala @@ -22,6 +22,18 @@ case class ReportConfig( case class FinalConfig(baseConfig: BaseConfig) extends Configuration -case class BaseConfig(projectToken: String, codacyApiBaseUrl: String, commitUUID: Option[CommitUUID], debug: Boolean) +sealed trait AuthenticationConfig + +case class ProjectTokenAuthenticationConfig(projectToken: String) extends AuthenticationConfig + +case class ApiTokenAuthenticationConfig(apiToken: String, username: String, projectName: String) + extends AuthenticationConfig + +case class BaseConfig( + authentication: AuthenticationConfig, + codacyApiBaseUrl: String, + commitUUID: Option[CommitUUID], + debug: Boolean +) case class CommitUUID(value: String) extends AnyVal diff --git a/src/main/scala/com/codacy/rules/ConfigurationRules.scala b/src/main/scala/com/codacy/rules/ConfigurationRules.scala index 83f42ec3..3c6cd795 100644 --- a/src/main/scala/com/codacy/rules/ConfigurationRules.scala +++ b/src/main/scala/com/codacy/rules/ConfigurationRules.scala @@ -4,21 +4,26 @@ import java.net.URL import java.io.File import com.codacy.configuration.parser.{BaseCommandConfig, CommandConfiguration, Final, Report} -import com.codacy.model.configuration.{BaseConfig, Configuration, FinalConfig, ReportConfig} +import com.codacy.model.configuration.{ + ApiTokenAuthenticationConfig, + AuthenticationConfig, + BaseConfig, + CommitUUID, + Configuration, + FinalConfig, + ProjectTokenAuthenticationConfig, + ReportConfig +} import com.typesafe.scalalogging.StrictLogging import scala.util.Try -import com.codacy.model.configuration.CommitUUID class ConfigurationRules(cmdConfig: CommandConfiguration) extends StrictLogging { private[rules] val publicApiBaseUrl = "https://api.codacy.com" - lazy val validatedConfig: Configuration = { + lazy val validatedConfig: Either[String, Configuration] = { val config = validateConfig(cmdConfig) - config.fold({ error => - logger.error(s"Invalid configuration: $error") - sys.exit(1) - }, identity) + config.left.map(error => s"Invalid configuration: $error") } private def validateConfig(cmdConfig: CommandConfiguration): Either[String, Configuration] = { @@ -34,7 +39,7 @@ class ConfigurationRules(cmdConfig: CommandConfiguration) extends StrictLogging private def validateFinalConfig(finalConfig: Final): Either[String, FinalConfig] = { for { - baseConfig <- validateBaseConfig(finalConfig.baseConfig) + baseConfig <- validateBaseConfig(finalConfig.baseConfig, sys.env) } yield FinalConfig(baseConfig) } @@ -50,7 +55,7 @@ class ConfigurationRules(cmdConfig: CommandConfiguration) extends StrictLogging } for { - baseConfig <- validateBaseConfig(reportConfig.baseConfig) + baseConfig <- validateBaseConfig(reportConfig.baseConfig, sys.env) validReportFiles <- validateReportFiles(reportConfig.coverageReports) reportConf = ReportConfig( baseConfig, @@ -65,38 +70,79 @@ class ConfigurationRules(cmdConfig: CommandConfiguration) extends StrictLogging } - private def validateBaseConfig(baseConfig: BaseCommandConfig): Either[String, BaseConfig] = { + private[rules] def validateBaseConfig( + baseConfig: BaseCommandConfig, + envVars: Map[String, String] + ): Either[String, BaseConfig] = { for { - projectToken <- baseConfig.projectToken.fold(getProjectToken(sys.env, baseConfig.skipValue))(Right(_)) + authConfig <- validateAuthConfig(baseConfig, envVars) baseConf = BaseConfig( - projectToken, + authConfig, baseConfig.codacyApiBaseUrl.getOrElse(getApiBaseUrl(sys.env)), - baseConfig.commitUUID.map(CommitUUID(_)), + baseConfig.commitUUID.map(CommitUUID), baseConfig.debugValue ) - validatedConfig <- { - baseConf match { - case config if !validUrl(config.codacyApiBaseUrl) => - val error = s"Invalid CODACY_API_BASE_URL: ${config.codacyApiBaseUrl}" - - val help = if (!config.codacyApiBaseUrl.startsWith("http")) { - "Maybe you forgot the http:// or https:// ?" - } - Left(s"$error\n$help") - - case config if config.projectToken.trim.isEmpty => - Left("Empty argument for --project-token") - - case _ => - Right(baseConf) - } - } + validatedConfig <- validateBaseConfigUrl(baseConf) } yield { - logger.info(s"Using API base URL: ${validatedConfig.codacyApiBaseUrl}") + logger.info(s"API base URL: ${validatedConfig.codacyApiBaseUrl}") validatedConfig } } + private def validateAuthConfig( + baseCommandConfig: BaseCommandConfig, + envVars: Map[String, String] + ): Either[String, AuthenticationConfig] = { + val errorMessage = + "Either a project token or an api token must be provided or available in an environment variable" + + val projectToken = getValueOrEnvironmentVar(baseCommandConfig.projectToken, envVars, "CODACY_PROJECT_TOKEN") + val apiToken = getValueOrEnvironmentVar(baseCommandConfig.apiToken, envVars, "CODACY_API_TOKEN") + + if (projectToken.isDefined) + validateProjectTokenAuth(projectToken) + else if (apiToken.isDefined) + validateApiTokenAuth(baseCommandConfig, apiToken, envVars) + else + Left(errorMessage) + } + + private def validateProjectTokenAuth(projectToken: Option[String]) = + projectToken.filter(_.nonEmpty) match { + case None => Left("Empty argument for --project-token") + case Some(projectToken) => Right(ProjectTokenAuthenticationConfig(projectToken)) + } + + private def validateApiTokenAuth( + baseCommandConfig: BaseCommandConfig, + apiToken: Option[String], + envVars: Map[String, String] + ) = + for { + apiToken <- apiToken.filter(_.nonEmpty).toRight("Empty argument --api-token") + username <- getValueOrEnvironmentVar(baseCommandConfig.username, envVars, "CODACY_USERNAME") + .filter(_.nonEmpty) + .toRight("Empty argument --username") + projectName <- getValueOrEnvironmentVar(baseCommandConfig.projectName, envVars, "CODACY_PROJECT_NAME") + .filter(_.nonEmpty) + .toRight("Empty argument --project-name") + } yield ApiTokenAuthenticationConfig(apiToken, username, projectName) + + private def getValueOrEnvironmentVar(value: Option[String], envVars: Map[String, String], envVarName: String) = + value.orElse(envVars.get(envVarName)) + + private def validateBaseConfigUrl(baseConfig: BaseConfig) = baseConfig match { + case config if !validUrl(config.codacyApiBaseUrl) => + val error = s"Invalid CODACY_API_BASE_URL: ${config.codacyApiBaseUrl}" + + val help = if (!config.codacyApiBaseUrl.startsWith("http")) { + "Maybe you forgot the http:// or https:// ?" + } + Left(s"""$error + |$help""".stripMargin) + case config => Right(config) + } + /** * Get API base URL * @@ -109,31 +155,6 @@ class ConfigurationRules(cmdConfig: CommandConfiguration) extends StrictLogging envVars.getOrElse("CODACY_API_BASE_URL", publicApiBaseUrl) } - /** - * Get project token - * - * This function try to get the project token from environment variables, and if not found - * return an error message. If the skip flag is true, it will exit the reporter with the normal state - * skipping the coverage to be reported. - * @param envVars environment variables - * @param skip skip flag - * @return the project token on the right or an error message on the left - */ - private[rules] def getProjectToken(envVars: Map[String, String], skip: Boolean): Either[String, String] = { - val projectToken = - envVars - .get("CODACY_PROJECT_TOKEN") - .toRight("Project token not provided and not available in environment variable \"CODACY_PROJECT_TOKEN\"") - - if (skip && projectToken.isLeft) { - logger.warn(projectToken.left.get) - logger.info("Skip reporting coverage") - sys.exit(0) - } else { - projectToken - } - } - /** * Validate an URL * @@ -156,7 +177,7 @@ class ConfigurationRules(cmdConfig: CommandConfiguration) extends StrictLogging filesOpt match { case Some(value) if value.isEmpty => Left("Invalid report list. Try passing a report file with -r") - case Some(value) if !value.isEmpty => + case Some(value) if value.nonEmpty => Right(value) case None => Right(List.empty[File]) diff --git a/src/main/scala/com/codacy/rules/ReportRules.scala b/src/main/scala/com/codacy/rules/ReportRules.scala index 6ba4a3f2..2e563ad3 100644 --- a/src/main/scala/com/codacy/rules/ReportRules.scala +++ b/src/main/scala/com/codacy/rules/ReportRules.scala @@ -7,17 +7,22 @@ import com.codacy.api.CoverageReport import com.codacy.api.client.{FailedResponse, SuccessfulResponse} import com.codacy.api.helpers.FileHelper import com.codacy.api.service.CoverageServices -import com.codacy.model.configuration.{BaseConfig, Configuration, FinalConfig, ReportConfig} +import com.codacy.model.configuration.{ + ApiTokenAuthenticationConfig, + BaseConfig, + FinalConfig, + ProjectTokenAuthenticationConfig, + ReportConfig +} import com.codacy.parsers.CoverageParser import com.codacy.transformation.PathPrefixer import com.typesafe.scalalogging.StrictLogging import com.codacy.plugins.api.languages.Languages - import com.codacy.rules.commituuid.CommitUUIDProvider import scala.collection.JavaConverters._ -class ReportRules(config: Configuration, coverageServices: => CoverageServices) extends StrictLogging { +class ReportRules(coverageServices: => CoverageServices) extends StrictLogging { private val rootProjectDir = new File(System.getProperty("user.dir")) private val rootProjectDirIterator = Files @@ -28,7 +33,7 @@ class ReportRules(config: Configuration, coverageServices: => CoverageServices) def codacyCoverage(config: ReportConfig): Either[String, String] = { withCommitUUID(config.baseConfig) { commitUUID => - logger.debug(s"Project token: ${config.baseConfig.projectToken}") + logAuthenticationToken(config) val filesEither = guessReportFiles(config.coverageReports, rootProjectDirIterator) @@ -57,6 +62,13 @@ class ReportRules(config: Configuration, coverageServices: => CoverageServices) } } + private def logAuthenticationToken(config: ReportConfig): Unit = { + config.baseConfig.authentication match { + case ProjectTokenAuthenticationConfig(projectToken) => logger.debug(s"Project token: $projectToken") + case ApiTokenAuthenticationConfig(apiToken, _, _) => logger.debug(s"Api token: $apiToken") + } + } + private[rules] def validateFileAccess(file: File) = { file match { case file if !file.exists => @@ -111,7 +123,14 @@ class ReportRules(config: Configuration, coverageServices: => CoverageServices) commitUUID: String, file: File ) = { - coverageServices.sendReport(commitUUID, language, report, config.partial) match { + val coverageResponse = config.baseConfig.authentication match { + case _: ProjectTokenAuthenticationConfig => + coverageServices.sendReport(commitUUID, language, report, config.partial) + + case ApiTokenAuthenticationConfig(_, username, projectName) => + coverageServices.sendReportWithProjectName(username, projectName, commitUUID, language, report, config.partial) + } + coverageResponse match { case SuccessfulResponse(value) => logger.info(s"Coverage data uploaded. ${value.success}") Right(()) diff --git a/src/test/resources/dotcover-example.xml b/src/test/resources/dotcover-example.xml new file mode 100644 index 00000000..5f7c7120 --- /dev/null +++ b/src/test/resources/dotcover-example.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/invalid-report.xml b/src/test/resources/invalid-report.xml new file mode 100644 index 00000000..9ae1b5fb --- /dev/null +++ b/src/test/resources/invalid-report.xml @@ -0,0 +1 @@ +invalid report diff --git a/src/test/scala/com/codacy/rules/ConfigurationRulesSpec.scala b/src/test/scala/com/codacy/rules/ConfigurationRulesSpec.scala index 5e3ab8d3..bc863fe8 100644 --- a/src/test/scala/com/codacy/rules/ConfigurationRulesSpec.scala +++ b/src/test/scala/com/codacy/rules/ConfigurationRulesSpec.scala @@ -15,14 +15,17 @@ class ConfigurationRulesSpec extends WordSpec with Matchers with OptionValues wi val coverageFiles = List(new File("coverage.xml")) val apiBaseUrl = "https://example.com" - val baseConf = BaseCommandConfig(Some(projToken), Some(apiBaseUrl), None) + val baseConf = BaseCommandConfig(Some(projToken), None, None, None, Some(apiBaseUrl), None) val conf = Report(baseConf, Some("Scala"), coverageReports = Some(coverageFiles), prefix = None) - val components = new Components(conf) + val configRules = new ConfigurationRules(conf) + val validatedConfig = configRules.validatedConfig.right.value + + val components = new Components(validatedConfig) "ConfigurationRules" should { "transform configuration" in { - inside(components.validatedConfig) { + inside(validatedConfig) { case config: ReportConfig => config.language.value should be(Languages.Scala) config.coverageReports.map(_.toString) should be(List("coverage.xml")) @@ -34,40 +37,107 @@ class ConfigurationRulesSpec extends WordSpec with Matchers with OptionValues wi } "check a valid url" in { - components.configRules.validUrl("https://example.com") should be(true) - components.configRules.validUrl("httt://example.com") should be(false) + configRules.validUrl("https://example.com") should be(true) + configRules.validUrl("httt://example.com") should be(false) } "validate report files" in { - val filesNoneOption = components.configRules.validateReportFiles(None) + val filesNoneOption = configRules.validateReportFiles(None) filesNoneOption should be('right) filesNoneOption.right.value should be(List.empty[File]) - val filesSomeInvalid = components.configRules.validateReportFiles(Some(List.empty[File])) + val filesSomeInvalid = configRules.validateReportFiles(Some(List.empty[File])) filesSomeInvalid should be('left) - val filesSomeValid = components.configRules.validateReportFiles(Some(coverageFiles)) + val filesSomeValid = configRules.validateReportFiles(Some(coverageFiles)) filesSomeValid should be('right) filesSomeValid.right.value should be(coverageFiles) } "get an api base url" in { val envVars = Map("CODACY_API_BASE_URL" -> apiBaseUrl) - val defaultBaseUrl = components.configRules.publicApiBaseUrl + val defaultBaseUrl = configRules.publicApiBaseUrl + + configRules.getApiBaseUrl(envVars) should be(apiBaseUrl) + configRules.getApiBaseUrl(Map.empty) should be(defaultBaseUrl) + } + } + + "validateBaseConfig" should { + "fail" when { + def assertFailure(baseCommandConfig: BaseCommandConfig) = { + val result = configRules.validateBaseConfig(baseCommandConfig, Map()) + result should be('left) + result + } + + "no token is used" in { + val baseConfig = + BaseCommandConfig(None, None, Some("username"), Some("projectName"), Some(apiBaseUrl), Some("CommitUUID")) + val result = assertFailure(baseConfig) + result.left.value should include("project token or an api token") + } + + "project token is empty" in { + val baseConfig = + BaseCommandConfig(Some(""), None, None, None, Some(apiBaseUrl), Some("CommitUUID")) + assertFailure(baseConfig) + } - components.configRules.getApiBaseUrl(envVars) should be(apiBaseUrl) - components.configRules.getApiBaseUrl(Map.empty) should be(defaultBaseUrl) + "api token is empty" in { + val baseConfig = + BaseCommandConfig(None, Some(""), None, None, Some(apiBaseUrl), Some("CommitUUID")) + assertFailure(baseConfig) + } + + "api token is used and username is not" in { + val baseConfig = + BaseCommandConfig(None, Some("token"), None, Some("projectName"), Some(apiBaseUrl), Some("CommitUUID")) + assertFailure(baseConfig) + } + + "api token is used and project name is not" in { + val baseConfig = + BaseCommandConfig(None, Some("token"), Some("username"), None, Some(apiBaseUrl), Some("CommitUUID")) + assertFailure(baseConfig) + } + + "API URL is invalid" in { + val baseConfig = + BaseCommandConfig(Some("projectToken"), None, None, None, Some("Invalid URL"), Some("CommitUUID")) + assertFailure(baseConfig) + } } - "get the project token" in { - val envVars = Map("CODACY_PROJECT_TOKEN" -> projToken) + "succeed" when { + "project token is used" in { + val baseConfig = + BaseCommandConfig(Some("token"), None, None, None, Some(apiBaseUrl), Some("CommitUUID")) + val result = configRules.validateBaseConfig(baseConfig, Map()) + result should be('right) + } - val validProjectToken = components.configRules.getProjectToken(envVars, false) - validProjectToken should be('right) - validProjectToken.right.value should be(projToken) + "api token and required fields are used" in { + val baseConfig = + BaseCommandConfig( + None, + Some("apiToken"), + Some("username"), + Some("projectName"), + Some(apiBaseUrl), + Some("CommitUUID") + ) + val result = configRules.validateBaseConfig(baseConfig, Map()) + result should be('right) + } - val noProjectToken = components.configRules.getProjectToken(Map.empty, false) - noProjectToken should be('left) + // it should use the project token only + "project token and api token are used" in { + val baseConfig = + BaseCommandConfig(Some("projectToken"), Some("apiToken"), None, None, Some(apiBaseUrl), Some("CommitUUID")) + val result = configRules.validateBaseConfig(baseConfig, Map()) + result should be('right) + } } } } diff --git a/src/test/scala/com/codacy/rules/ReportRulesSpec.scala b/src/test/scala/com/codacy/rules/ReportRulesSpec.scala index e88a192f..557de77c 100644 --- a/src/test/scala/com/codacy/rules/ReportRulesSpec.scala +++ b/src/test/scala/com/codacy/rules/ReportRulesSpec.scala @@ -1,27 +1,89 @@ package com.codacy.rules -import org.scalatest._ - import java.io.File +import com.codacy.api.client.{FailedResponse, RequestSuccess, SuccessfulResponse} +import com.codacy.api.service.CoverageServices +import com.codacy.api.{CoverageFileReport, CoverageReport} import com.codacy.configuration.parser.{BaseCommandConfig, Report} import com.codacy.di.Components -import com.codacy.api.client.FailedResponse -import com.codacy.api.{CoverageFileReport, CoverageReport} +import com.codacy.model.configuration.{BaseConfig, CommitUUID, ProjectTokenAuthenticationConfig, ReportConfig} import com.codacy.plugins.api.languages.Languages +import org.mockito.scalatest.IdiomaticMockito +import org.scalatest._ -class ReportRulesSpec extends WordSpec with Matchers with PrivateMethodTester with EitherValues { +class ReportRulesSpec extends WordSpec with Matchers with PrivateMethodTester with EitherValues with IdiomaticMockito { val projToken = "1234adasdsdw333" val coverageFiles = List(new File("coverage.xml")) val apiBaseUrl = "https://api.codacy.com" - val baseConf = BaseCommandConfig(Some(projToken), Some(apiBaseUrl), None) + val commitUUID = CommitUUID("commitUUID") + + val baseConf = BaseCommandConfig(Some(projToken), None, None, None, Some(apiBaseUrl), None) val conf = Report(baseConf, Some("Scala"), coverageReports = Some(coverageFiles), prefix = None) val coverageReport = CoverageReport(100, Seq(CoverageFileReport("file.scala", 100, Map(10 -> 1)))) val noLanguageReport = CoverageReport(0, Seq.empty[CoverageFileReport]) - val components = new Components(conf) + val configRules = new ConfigurationRules(conf) + val validatedConfig = configRules.validatedConfig.right.value + + val components = new Components(validatedConfig) + + "codacyCoverage" should { + val baseConfig = + BaseConfig(ProjectTokenAuthenticationConfig(projToken), apiBaseUrl, Some(commitUUID), debug = false) + + def assertCodacyCoverage(coverageServices: CoverageServices, coverageReports: List[String], success: Boolean) = { + val reportRules = new ReportRules(coverageServices) + val reportConfig = + ReportConfig( + baseConfig, + None, + forceLanguage = false, + coverageReports = coverageReports.map(new File(_)), + partial = false, + prefix = "" + ) + val result = reportRules.codacyCoverage(reportConfig) + + result should be(if (success) 'right else 'left) + } + + "fail" when { + "it finds no report file" in { + val coverageServices = mock[CoverageServices] + + assertCodacyCoverage(coverageServices, List(), success = false) + } + + "it is not able to parse report file" in { + val coverageServices = mock[CoverageServices] + + assertCodacyCoverage(coverageServices, List("src/test/resources/invalid-report.xml"), success = false) + } + + "cannot send report" in { + val coverageServices = mock[CoverageServices] + + coverageServices.sendReport(any[String], any[String], any[CoverageReport], anyBoolean) returns FailedResponse( + "Failed to send report" + ) + + assertCodacyCoverage(coverageServices, List("src/test/resources/dotcover-example.xml"), success = false) + } + } + + "succeed if it can parse and send the report" in { + val coverageServices = mock[CoverageServices] + + coverageServices.sendReport(any[String], any[String], any[CoverageReport], anyBoolean) returns SuccessfulResponse( + RequestSuccess("Success") + ) + + assertCodacyCoverage(coverageServices, List("src/test/resources/dotcover-example.xml"), success = true) + } + } "handleFailedResponse" should { "provide a different message" in { @@ -127,7 +189,7 @@ class ReportRulesSpec extends WordSpec with Matchers with PrivateMethodTester wi result should be('left) } - "report is stored" when { + "successfully store report" when { def storeValidReport() = { val emptyReport = CoverageReport(0, List(CoverageFileReport("file-name", 0, Map()))) val tempFile = File.createTempFile("storeReport", "not-store") @@ -139,10 +201,10 @@ class ReportRulesSpec extends WordSpec with Matchers with PrivateMethodTester wi result should be('right) } - "store report" in { + "store report exists" in { val result = storeValidReport() val resultFile = new File(result.right.value) - resultFile.exists should be(true) + resultFile should be('exists) } } }