mirror of https://github.com/sbt/sbt.git
[sonatype publishing] print deployment validation errors if present
Before this change you had to log into the sonatype account and search for the errors there. (https://central.sonatype.com/publishing/deployments) This was inconvenient, especially if you don't have the admin access to the account.
This commit is contained in:
parent
5ee54ac60c
commit
b83eee4528
|
|
@ -9,22 +9,23 @@ final class PublisherStatus private (
|
||||||
val deploymentId: String,
|
val deploymentId: String,
|
||||||
val deploymentName: String,
|
val deploymentName: String,
|
||||||
val deploymentState: sbt.internal.sona.DeploymentState,
|
val deploymentState: sbt.internal.sona.DeploymentState,
|
||||||
val purls: Vector[String]) extends Serializable {
|
val purls: Vector[String],
|
||||||
|
val errors: Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue]) extends Serializable {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
override def equals(o: Any): Boolean = this.eq(o.asInstanceOf[AnyRef]) || (o match {
|
override def equals(o: Any): Boolean = this.eq(o.asInstanceOf[AnyRef]) || (o match {
|
||||||
case x: PublisherStatus => (this.deploymentId == x.deploymentId) && (this.deploymentName == x.deploymentName) && (this.deploymentState == x.deploymentState) && (this.purls == x.purls)
|
case x: PublisherStatus => (this.deploymentId == x.deploymentId) && (this.deploymentName == x.deploymentName) && (this.deploymentState == x.deploymentState) && (this.purls == x.purls) && (this.errors == x.errors)
|
||||||
case _ => false
|
case _ => false
|
||||||
})
|
})
|
||||||
override def hashCode: Int = {
|
override def hashCode: Int = {
|
||||||
37 * (37 * (37 * (37 * (37 * (17 + "sbt.internal.sona.PublisherStatus".##) + deploymentId.##) + deploymentName.##) + deploymentState.##) + purls.##)
|
37 * (37 * (37 * (37 * (37 * (37 * (17 + "sbt.internal.sona.PublisherStatus".##) + deploymentId.##) + deploymentName.##) + deploymentState.##) + purls.##) + errors.##)
|
||||||
}
|
}
|
||||||
override def toString: String = {
|
override def toString: String = {
|
||||||
"PublisherStatus(" + deploymentId + ", " + deploymentName + ", " + deploymentState + ", " + purls + ")"
|
"PublisherStatus(" + deploymentId + ", " + deploymentName + ", " + deploymentState + ", " + purls + ", " + errors + ")"
|
||||||
}
|
}
|
||||||
private[this] def copy(deploymentId: String = deploymentId, deploymentName: String = deploymentName, deploymentState: sbt.internal.sona.DeploymentState = deploymentState, purls: Vector[String] = purls): PublisherStatus = {
|
private[this] def copy(deploymentId: String = deploymentId, deploymentName: String = deploymentName, deploymentState: sbt.internal.sona.DeploymentState = deploymentState, purls: Vector[String] = purls, errors: Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue] = errors): PublisherStatus = {
|
||||||
new PublisherStatus(deploymentId, deploymentName, deploymentState, purls)
|
new PublisherStatus(deploymentId, deploymentName, deploymentState, purls, errors)
|
||||||
}
|
}
|
||||||
def withDeploymentId(deploymentId: String): PublisherStatus = {
|
def withDeploymentId(deploymentId: String): PublisherStatus = {
|
||||||
copy(deploymentId = deploymentId)
|
copy(deploymentId = deploymentId)
|
||||||
|
|
@ -38,8 +39,15 @@ final class PublisherStatus private (
|
||||||
def withPurls(purls: Vector[String]): PublisherStatus = {
|
def withPurls(purls: Vector[String]): PublisherStatus = {
|
||||||
copy(purls = purls)
|
copy(purls = purls)
|
||||||
}
|
}
|
||||||
|
def withErrors(errors: Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue]): PublisherStatus = {
|
||||||
|
copy(errors = errors)
|
||||||
|
}
|
||||||
|
def withErrors(errors: sjsonnew.shaded.scalajson.ast.unsafe.JValue): PublisherStatus = {
|
||||||
|
copy(errors = Option(errors))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
object PublisherStatus {
|
object PublisherStatus {
|
||||||
|
|
||||||
def apply(deploymentId: String, deploymentName: String, deploymentState: sbt.internal.sona.DeploymentState, purls: Vector[String]): PublisherStatus = new PublisherStatus(deploymentId, deploymentName, deploymentState, purls)
|
def apply(deploymentId: String, deploymentName: String, deploymentState: sbt.internal.sona.DeploymentState, purls: Vector[String], errors: Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue]): PublisherStatus = new PublisherStatus(deploymentId, deploymentName, deploymentState, purls, errors)
|
||||||
|
def apply(deploymentId: String, deploymentName: String, deploymentState: sbt.internal.sona.DeploymentState, purls: Vector[String], errors: sjsonnew.shaded.scalajson.ast.unsafe.JValue): PublisherStatus = new PublisherStatus(deploymentId, deploymentName, deploymentState, purls, Option(errors))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,6 @@
|
||||||
package sbt.internal.sona.codec
|
package sbt.internal.sona.codec
|
||||||
trait JsonProtocol extends sjsonnew.BasicJsonProtocol
|
trait JsonProtocol extends sjsonnew.BasicJsonProtocol
|
||||||
with sbt.internal.sona.codec.DeploymentStateFormats
|
with sbt.internal.sona.codec.DeploymentStateFormats
|
||||||
|
with sbt.internal.util.codec.JValueFormats
|
||||||
with sbt.internal.sona.codec.PublisherStatusFormats
|
with sbt.internal.sona.codec.PublisherStatusFormats
|
||||||
object JsonProtocol extends JsonProtocol
|
object JsonProtocol extends JsonProtocol
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
// DO NOT EDIT MANUALLY
|
// DO NOT EDIT MANUALLY
|
||||||
package sbt.internal.sona.codec
|
package sbt.internal.sona.codec
|
||||||
import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError }
|
import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError }
|
||||||
trait PublisherStatusFormats { self: sbt.internal.sona.codec.DeploymentStateFormats with sjsonnew.BasicJsonProtocol =>
|
trait PublisherStatusFormats { self: sbt.internal.sona.codec.DeploymentStateFormats with sjsonnew.BasicJsonProtocol with sbt.internal.util.codec.JValueFormats =>
|
||||||
implicit lazy val PublisherStatusFormat: JsonFormat[sbt.internal.sona.PublisherStatus] = new JsonFormat[sbt.internal.sona.PublisherStatus] {
|
implicit lazy val PublisherStatusFormat: JsonFormat[sbt.internal.sona.PublisherStatus] = new JsonFormat[sbt.internal.sona.PublisherStatus] {
|
||||||
override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.sona.PublisherStatus = {
|
override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.sona.PublisherStatus = {
|
||||||
__jsOpt match {
|
__jsOpt match {
|
||||||
|
|
@ -15,8 +15,9 @@ implicit lazy val PublisherStatusFormat: JsonFormat[sbt.internal.sona.PublisherS
|
||||||
val deploymentName = unbuilder.readField[String]("deploymentName")
|
val deploymentName = unbuilder.readField[String]("deploymentName")
|
||||||
val deploymentState = unbuilder.readField[sbt.internal.sona.DeploymentState]("deploymentState")
|
val deploymentState = unbuilder.readField[sbt.internal.sona.DeploymentState]("deploymentState")
|
||||||
val purls = unbuilder.readField[Vector[String]]("purls")
|
val purls = unbuilder.readField[Vector[String]]("purls")
|
||||||
|
val errors = unbuilder.readField[Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue]]("errors")
|
||||||
unbuilder.endObject()
|
unbuilder.endObject()
|
||||||
sbt.internal.sona.PublisherStatus(deploymentId, deploymentName, deploymentState, purls)
|
sbt.internal.sona.PublisherStatus(deploymentId, deploymentName, deploymentState, purls, errors)
|
||||||
case None =>
|
case None =>
|
||||||
deserializationError("Expected JsObject but found None")
|
deserializationError("Expected JsObject but found None")
|
||||||
}
|
}
|
||||||
|
|
@ -27,6 +28,7 @@ implicit lazy val PublisherStatusFormat: JsonFormat[sbt.internal.sona.PublisherS
|
||||||
builder.addField("deploymentName", obj.deploymentName)
|
builder.addField("deploymentName", obj.deploymentName)
|
||||||
builder.addField("deploymentState", obj.deploymentState)
|
builder.addField("deploymentState", obj.deploymentState)
|
||||||
builder.addField("purls", obj.purls)
|
builder.addField("purls", obj.purls)
|
||||||
|
builder.addField("errors", obj.errors)
|
||||||
builder.endObject()
|
builder.endObject()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,4 +18,6 @@ type PublisherStatus {
|
||||||
deploymentName: String!
|
deploymentName: String!
|
||||||
deploymentState: sbt.internal.sona.DeploymentState!
|
deploymentState: sbt.internal.sona.DeploymentState!
|
||||||
purls: [String]
|
purls: [String]
|
||||||
|
# Optional errors. The field has non-standard structure and thus we avoid automatic format generation
|
||||||
|
errors: sjsonnew.shaded.scalajson.ast.unsafe.JValue
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* sbt
|
||||||
|
* Copyright 2023, Scala center
|
||||||
|
* Copyright 2011 - 2022, Lightbend, Inc.
|
||||||
|
* Copyright 2008 - 2010, Mark Harrah
|
||||||
|
* Licensed under Apache License 2.0 (see LICENSE)
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sbt.internal.sona
|
||||||
|
|
||||||
|
import sjsonnew.shaded.scalajson.ast.unsafe.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents validation errors for one of the deployed packages in case deployment to sonatype has failed
|
||||||
|
*
|
||||||
|
* @param packageDescriptor package descriptor<br>
|
||||||
|
* (e.g. "pkg:maven/org.example.company/sbt-plugin-core_2.12_1.0@0.0.6")
|
||||||
|
* @param packageErrors list of validation errors for the package<br>
|
||||||
|
* (e.g. ""Component with package url: 'pkg:maven/org.example.company/sbt-plugin-core_2.12_1.0@0.0.6' already exists")
|
||||||
|
* @see https://central.sonatype.org/publish/publish-portal-api/#verify-status-of-the-deployment
|
||||||
|
*/
|
||||||
|
private case class PackageDeploymentValidationError(
|
||||||
|
packageDescriptor: String,
|
||||||
|
packageErrors: Seq[String]
|
||||||
|
)
|
||||||
|
|
||||||
|
private object PackageDeploymentValidationError {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example: (it's not an array but an object which makes it hard to parse with the standard contraband means)
|
||||||
|
* {{{
|
||||||
|
* {
|
||||||
|
* <OTHER_FIELDS>,
|
||||||
|
* "errors": {
|
||||||
|
* "pkg:maven/org.example.company/sbt-plugin-core_2.12_1.0@0.0.6": [
|
||||||
|
* "Component with package url: 'pkg:maven/org.example.company/sbt-plugin-core_2.12_1.0@0.0.6' already exists"
|
||||||
|
* ],
|
||||||
|
* "pkg:maven/org.example.company/sbt-plugin-extra_2.12_1.0@0.0.6": [
|
||||||
|
* "Component with package url: 'pkg:maven/org.example.company/sbt-plugin-extra_2.12_1.0@0.0.6' already exists"
|
||||||
|
* ]
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }}}
|
||||||
|
*
|
||||||
|
* @param errorsNode - the JSON node that contains the errors
|
||||||
|
* @return Some(errors) - if the JSON structure matches our expectations<br>
|
||||||
|
* None - otherwise (Sonatype Central could change the format of the output)
|
||||||
|
*/
|
||||||
|
def parse(errorsNode: JValue): Option[Seq[PackageDeploymentValidationError]] =
|
||||||
|
errorsNode match {
|
||||||
|
case JObject(fields) =>
|
||||||
|
val errors = fields.toSeq.flatMap {
|
||||||
|
case JField(packageInfo, JArray(packageErrors)) =>
|
||||||
|
val packageErrorsTexts = packageErrors.flatMap {
|
||||||
|
case JString(value) => Some(value)
|
||||||
|
case other => None
|
||||||
|
}
|
||||||
|
val noParsingIssues = packageErrors.length == packageErrorsTexts.length
|
||||||
|
if (noParsingIssues)
|
||||||
|
Some(PackageDeploymentValidationError(packageInfo, packageErrorsTexts))
|
||||||
|
else
|
||||||
|
None
|
||||||
|
case _ =>
|
||||||
|
None
|
||||||
|
}
|
||||||
|
val noParsingIssues = errors.size == fields.length
|
||||||
|
if (noParsingIssues)
|
||||||
|
Some(errors)
|
||||||
|
else
|
||||||
|
None
|
||||||
|
case _ =>
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,10 +12,11 @@ package sona
|
||||||
|
|
||||||
import gigahorse.*
|
import gigahorse.*
|
||||||
import gigahorse.support.apachehttp.Gigahorse
|
import gigahorse.support.apachehttp.Gigahorse
|
||||||
|
import sbt.internal.sona.SonaClient.failedDeploymentErrorText
|
||||||
import sbt.util.Logger
|
import sbt.util.Logger
|
||||||
import sjsonnew.JsonFormat
|
import sjsonnew.JsonFormat
|
||||||
import sjsonnew.shaded.scalajson.ast.unsafe.JValue
|
import sjsonnew.shaded.scalajson.ast.unsafe.JValue
|
||||||
import sjsonnew.support.scalajson.unsafe.{ Converter, Parser }
|
import sjsonnew.support.scalajson.unsafe.{ Converter, Parser, PrettyPrinter }
|
||||||
|
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
|
|
@ -107,7 +108,9 @@ class SonaClient(reqTransform: Request => Request, uploadRequestTimeout: FiniteD
|
||||||
if (attempt <= 3) List(5, 5, 10, 15)(attempt)
|
if (attempt <= 3) List(5, 5, 10, 15)(attempt)
|
||||||
else 30
|
else 30
|
||||||
status.deploymentState match {
|
status.deploymentState match {
|
||||||
case DeploymentState.FAILED => sys.error(s"deployment $deploymentId failed")
|
case DeploymentState.FAILED =>
|
||||||
|
val errorText = failedDeploymentErrorText(deploymentId, status.errors, log)
|
||||||
|
sys.error(errorText)
|
||||||
case DeploymentState.PENDING | DeploymentState.PUBLISHING | DeploymentState.VALIDATING =>
|
case DeploymentState.PENDING | DeploymentState.PUBLISHING | DeploymentState.VALIDATING =>
|
||||||
Thread.sleep(sleepSec * 1000L)
|
Thread.sleep(sleepSec * 1000L)
|
||||||
waitForDeploy(deploymentId, deploymentName, publishingType, attempt + 1, log)
|
waitForDeploy(deploymentId, deploymentName, publishingType, attempt + 1, log)
|
||||||
|
|
@ -204,6 +207,54 @@ object SonaClient {
|
||||||
uploadRequestTimeout: FiniteDuration
|
uploadRequestTimeout: FiniteDuration
|
||||||
): SonaClient =
|
): SonaClient =
|
||||||
new SonaClient(OAuthClient(userName, userToken), uploadRequestTimeout)
|
new SonaClient(OAuthClient(userName, userToken), uploadRequestTimeout)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @note non-private visibility only for the tests
|
||||||
|
*/
|
||||||
|
private[sona] def failedDeploymentErrorText(
|
||||||
|
deploymentId: String,
|
||||||
|
errors: Option[JValue],
|
||||||
|
log: Logger
|
||||||
|
): String = {
|
||||||
|
val errorsText = errors.map(presentDeploymentValidationErrors(_, log))
|
||||||
|
val errorsMessagePart = errorsText match {
|
||||||
|
case Some(value) =>
|
||||||
|
s" with validation errors:\n$value"
|
||||||
|
case None => ""
|
||||||
|
}
|
||||||
|
s"deployment $deploymentId failed$errorsMessagePart"
|
||||||
|
}
|
||||||
|
|
||||||
|
import sbt.internal.sona.SonaClient.PrettyPrint.*
|
||||||
|
|
||||||
|
private def presentDeploymentValidationErrors(errorsNode: JValue, log: Logger): String = {
|
||||||
|
PackageDeploymentValidationError.parse(errorsNode) match {
|
||||||
|
case Some(errors) =>
|
||||||
|
val errorsPresented: Seq[String] = errors.map {
|
||||||
|
case PackageDeploymentValidationError(packageDescriptor, packageErrors) =>
|
||||||
|
s"""$packageDescriptor
|
||||||
|
|${indent(asList(packageErrors), 2)}""".stripMargin
|
||||||
|
}
|
||||||
|
indent(asList(errorsPresented), 2)
|
||||||
|
case None =>
|
||||||
|
// Sonatype might change the format of the errors in the future.
|
||||||
|
// We shouldn't fail, and as a fallback we pretty print the JSON representation
|
||||||
|
log.warn(
|
||||||
|
"Sonatype deployment validation errors JSON format has changed. Please update to the latest sbt version or report the issue to the sbt project"
|
||||||
|
)
|
||||||
|
PrettyPrinter(errorsNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private object PrettyPrint {
|
||||||
|
def asList(lines: Seq[String]): String =
|
||||||
|
lines.map("- " + _).mkString("\n")
|
||||||
|
|
||||||
|
def indent(text: String, indentSize: Int): String = {
|
||||||
|
val indent = " " * indentSize
|
||||||
|
text.linesIterator.map(indent + _).mkString("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private case class OAuthClient(userName: String, userToken: String)
|
private case class OAuthClient(userName: String, userToken: String)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
* sbt
|
||||||
|
* Copyright 2023, Scala center
|
||||||
|
* Copyright 2011 - 2022, Lightbend, Inc.
|
||||||
|
* Copyright 2008 - 2010, Mark Harrah
|
||||||
|
* Licensed under Apache License 2.0 (see LICENSE)
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sbt.internal.sona
|
||||||
|
|
||||||
|
import org.scalatest.flatspec.AnyFlatSpec
|
||||||
|
import org.scalatest.matchers.should.Matchers.convertToAnyShouldWrapper
|
||||||
|
import sbt.internal.sona.SonaClientTest.RecordingLogger
|
||||||
|
import sbt.internal.util.BasicLogger
|
||||||
|
import sbt.util.*
|
||||||
|
import sjsonnew.support.scalajson.unsafe.Parser
|
||||||
|
|
||||||
|
import scala.collection.immutable
|
||||||
|
|
||||||
|
class SonaClientTest extends AnyFlatSpec {
|
||||||
|
|
||||||
|
private def doTest(
|
||||||
|
errorsJsonText: Option[String],
|
||||||
|
expectedErrorMessage: String,
|
||||||
|
expectedLogText: String = ""
|
||||||
|
): Unit = {
|
||||||
|
val logger = new RecordingLogger()
|
||||||
|
val errorsNode = errorsJsonText.map(Parser.parseUnsafe)
|
||||||
|
val result = SonaClient.failedDeploymentErrorText(
|
||||||
|
deploymentId = "12345",
|
||||||
|
errors = errorsNode,
|
||||||
|
log = logger
|
||||||
|
)
|
||||||
|
result shouldBe expectedErrorMessage
|
||||||
|
|
||||||
|
val actualLogText = logger.getLogMessages.mkString("\n")
|
||||||
|
actualLogText shouldBe expectedLogText
|
||||||
|
|
||||||
|
() //to avoid the "discarded non-Unit" value warning
|
||||||
|
}
|
||||||
|
|
||||||
|
it should "construct a failed deployment error message without errors" in doTest(
|
||||||
|
None,
|
||||||
|
"""deployment 12345 failed""".stripMargin
|
||||||
|
)
|
||||||
|
|
||||||
|
it should "construct a failed deployment error message with validation errors" in doTest(
|
||||||
|
Some(
|
||||||
|
"""{
|
||||||
|
| "pkg:maven/org.example.company/sbt-plugin-core_2.12_1.0@0.0.6": [
|
||||||
|
| "Component with package url: 'pkg:maven/org.example.company/sbt-plugin-core_2.12_1.0@0.0.6' already exists"
|
||||||
|
| ],
|
||||||
|
| "pkg:maven/org.example.company/sbt-plugin-extra_2.12_1.0@0.0.6": [
|
||||||
|
| "Component with package url: 'pkg:maven/org.example.company/sbt-plugin-extra_2.12_1.0@0.0.6' some reason 1",
|
||||||
|
| "Component with package url: 'pkg:maven/org.example.company/sbt-plugin-extra_2.12_1.0@0.0.6' some reason 2"
|
||||||
|
| ]
|
||||||
|
|}""".stripMargin
|
||||||
|
),
|
||||||
|
"""deployment 12345 failed with validation errors:
|
||||||
|
| - pkg:maven/org.example.company/sbt-plugin-core_2.12_1.0@0.0.6
|
||||||
|
| - Component with package url: 'pkg:maven/org.example.company/sbt-plugin-core_2.12_1.0@0.0.6' already exists
|
||||||
|
| - pkg:maven/org.example.company/sbt-plugin-extra_2.12_1.0@0.0.6
|
||||||
|
| - Component with package url: 'pkg:maven/org.example.company/sbt-plugin-extra_2.12_1.0@0.0.6' some reason 1
|
||||||
|
| - Component with package url: 'pkg:maven/org.example.company/sbt-plugin-extra_2.12_1.0@0.0.6' some reason 2""".stripMargin
|
||||||
|
)
|
||||||
|
|
||||||
|
it should "construct a failed deployment error message with validation errors in an unknown format" in doTest(
|
||||||
|
Some(
|
||||||
|
"""[
|
||||||
|
| {
|
||||||
|
| "package" : "pkg:maven/org.example.company/sbt-plugin-core_2.12_1.0@0.0.6",
|
||||||
|
| "errors" : [
|
||||||
|
| "Component with package url: 'pkg:maven/org.example.company/sbt-plugin-core_2.12_1.0@0.0.6' already exists"
|
||||||
|
| ]
|
||||||
|
| },
|
||||||
|
| {
|
||||||
|
| "package" : "pkg:maven/org.example.company/sbt-plugin-extra_2.12_1.0@0.0.6",
|
||||||
|
| "errors" : [
|
||||||
|
| "Component with package url: 'pkg:maven/org.example.company/sbt-plugin-extra_2.12_1.0@0.0.6' some reason 1",
|
||||||
|
| "Component with package url: 'pkg:maven/org.example.company/sbt-plugin-extra_2.12_1.0@0.0.6' some reason 2"
|
||||||
|
| ]
|
||||||
|
| }
|
||||||
|
|]""".stripMargin
|
||||||
|
),
|
||||||
|
"""deployment 12345 failed with validation errors:
|
||||||
|
|[{
|
||||||
|
| "package": "pkg:maven/org.example.company/sbt-plugin-core_2.12_1.0@0.0.6",
|
||||||
|
| "errors": ["Component with package url: 'pkg:maven/org.example.company/sbt-plugin-core_2.12_1.0@0.0.6' already exists"]
|
||||||
|
|}, {
|
||||||
|
| "package": "pkg:maven/org.example.company/sbt-plugin-extra_2.12_1.0@0.0.6",
|
||||||
|
| "errors": ["Component with package url: 'pkg:maven/org.example.company/sbt-plugin-extra_2.12_1.0@0.0.6' some reason 1", "Component with package url: 'pkg:maven/org.example.company/sbt-plugin-extra_2.12_1.0@0.0.6' some reason 2"]
|
||||||
|
|}]""".stripMargin,
|
||||||
|
expectedLogText =
|
||||||
|
"[warn] Sonatype deployment validation errors JSON format has changed. Please update to the latest sbt version or report the issue to the sbt project"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
object SonaClientTest {
|
||||||
|
|
||||||
|
implicit class RecordingLoggerOps(private val value: RecordingLogger) extends AnyVal {
|
||||||
|
def getLogMessages: immutable.Seq[String] =
|
||||||
|
value.getEvents.collect { case l: Log => s"[${l.level}] ${l.msg}" }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records logging events for later retrieval.
|
||||||
|
*
|
||||||
|
* @note This is a copy of a logger from the "util-logging" module tests.
|
||||||
|
* Instead of copying we could depend on the module test directly or extract it into some test-utilities module.
|
||||||
|
*/
|
||||||
|
final class RecordingLogger extends BasicLogger {
|
||||||
|
private var events: List[LogEvent] = Nil
|
||||||
|
|
||||||
|
def getEvents = events.reverse
|
||||||
|
|
||||||
|
override def ansiCodesSupported = true
|
||||||
|
def trace(t: => Throwable): Unit = { events ::= new Trace(t) }
|
||||||
|
def log(level: Level.Value, message: => String): Unit = { events ::= new Log(level, message) }
|
||||||
|
def success(message: => String): Unit = { events ::= new Success(message) }
|
||||||
|
def logAll(es: Seq[LogEvent]): Unit = { events :::= es.toList }
|
||||||
|
|
||||||
|
def control(event: ControlEvent.Value, message: => String): Unit =
|
||||||
|
events ::= new ControlEvent(event, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue