Skip to content

Commit

Permalink
Improve scenario testing error messages (#7591)
Browse files Browse the repository at this point in the history
  • Loading branch information
jedrz authored Mar 4, 2025
1 parent 613fada commit 429a4dd
Show file tree
Hide file tree
Showing 22 changed files with 528 additions and 235 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,13 @@ import pl.touk.nussknacker.engine.api.component.NodesDeploymentData
import pl.touk.nussknacker.engine.api.deployment._
import pl.touk.nussknacker.engine.api.deployment.DeploymentUpdateStrategy.StateRestoringStrategy
import pl.touk.nussknacker.engine.api.graph.ScenarioGraph
import pl.touk.nussknacker.engine.definition.test.TestInfoProvider.{
ScenarioTestDataGenerationError,
TestDataPreparationError
}
import pl.touk.nussknacker.engine.testmode.TestProcess._
import pl.touk.nussknacker.restmodel.{CancelRequest, DeployRequest, RunOffScheduleRequest, RunOffScheduleResponse}
import pl.touk.nussknacker.ui.{BadRequestError, OtherError}
import pl.touk.nussknacker.ui.api.ProcessesResources.ProcessUnmarshallingError
import pl.touk.nussknacker.ui.api.description.NodesApiEndpoints.Dtos.AdhocTestParametersRequest
import pl.touk.nussknacker.ui.metrics.TimeMeasuring.measureTime
Expand All @@ -25,6 +30,9 @@ import pl.touk.nussknacker.ui.process.deployment._
import pl.touk.nussknacker.ui.process.deployment.LoggedUserConversions.LoggedUserOps
import pl.touk.nussknacker.ui.process.processingtype.provider.ProcessingTypeDataProvider
import pl.touk.nussknacker.ui.process.test.{RawScenarioTestData, ResultsWithCounts, ScenarioTestService}
import pl.touk.nussknacker.ui.process.test.PreliminaryScenarioTestDataSerDe.{DeserializationError, SerializationError}
import pl.touk.nussknacker.ui.process.test.PreliminaryScenarioTestDataSerDe.DeserializationError.TooManySamples
import pl.touk.nussknacker.ui.process.test.ScenarioTestService.{GenerateTestDataError, PerformTestError}
import pl.touk.nussknacker.ui.security.api.LoggedUser

import scala.concurrent.{ExecutionContext, Future}
Expand Down Expand Up @@ -60,6 +68,62 @@ object ManagementResources {

}

final case class GenerateTestDataDesignerError(message: String) extends BadRequestError(message)

private object GenerateTestDataDesignerError {

def apply(generateTestDataError: ScenarioTestService.GenerateTestDataError): GenerateTestDataDesignerError = {
GenerateTestDataDesignerError(generateTestDataError match {
case GenerateTestDataError.ScenarioTestDataGenerationError(cause) =>
cause match {
case ScenarioTestDataGenerationError.NoDataGenerated =>
TestingApiErrorMessages.generatedTestData.couldNotProvideTestDataSample
case ScenarioTestDataGenerationError.NoSourcesWithTestDataGeneration =>
TestingApiErrorMessages.generatedTestData.noSourcesWithTestDataGeneration
}
case GenerateTestDataError.ScenarioTestDataSerializationError(cause) =>
cause match {
case SerializationError.TooManyCharactersGenerated(length, limit) =>
TestingApiErrorMessages.generatedTestData.tooManyCharacters(length, limit)
}
case GenerateTestDataError.TooManySamplesRequestedError(maxSamples) =>
TestingApiErrorMessages.generatedTestData.requestedTooManySamplesToGenerate(maxSamples)
})
}

}

final case class PerformTestDesignerError(message: String) extends BadRequestError(message)

private object PerformTestDesignerError {

def apply(performTestError: ScenarioTestService.PerformTestError): PerformTestDesignerError = {
PerformTestDesignerError(performTestError match {
case PerformTestError.DeserializationError(cause) =>
cause match {
case DeserializationError.TooManyCharacters(length, limit) =>
TestingApiErrorMessages.passedTestData.tooManyCharacters(length, limit)
case DeserializationError.TooManySamples(size, limit) =>
TestingApiErrorMessages.passedTestData.tooManySamples(size, limit)
case DeserializationError.NoRecords =>
TestingApiErrorMessages.passedTestData.empty
case DeserializationError.RecordParsingError(rawTestRecord, recordIndex) =>
TestingApiErrorMessages.problemInSample(recordIndex).parsingError(rawTestRecord)
}
case PerformTestError.TestDataPreparationError(cause) =>
cause match {
case TestDataPreparationError.MissingSource(sourceId, recordIndex) =>
TestingApiErrorMessages.problemInSample(recordIndex).missingSource(sourceId)
case TestDataPreparationError.MultipleSourcesRequired(recordIndex) =>
TestingApiErrorMessages.problemInSample(recordIndex).multipleSourcesRequired
}
case PerformTestError.TestResultsSizeExceeded(approxSizeInBytes, maxBytes) =>
TestingApiErrorMessages.testResultsSizeExceeded(approxSizeInBytes, maxBytes)
})
}

}

}

class ManagementResources(
Expand Down Expand Up @@ -235,7 +299,10 @@ class ManagementResources(
details.isFragment,
RawScenarioTestData(testDataContent)
)
.flatMap(mapResultsToHttpResponse)
.flatMap {
case Left(error) => Future.failed(PerformTestDesignerError(error))
case Right(value) => mapResultsToHttpResponse(value)
}
case Left(error) =>
Future.failed(ProcessUnmarshallingError(error.toString))
}
Expand All @@ -260,7 +327,7 @@ class ManagementResources(
details.isFragment,
testSampleSize
) match {
case Left(error) => Future.failed(ProcessUnmarshallingError(error))
case Left(error) => Future.failed(GenerateTestDataDesignerError(error))
case Right(rawScenarioTestData) =>
scenarioTestService
.performTest(
Expand All @@ -269,7 +336,10 @@ class ManagementResources(
details.isFragment,
rawScenarioTestData
)
.flatMap(mapResultsToHttpResponse)
.flatMap {
case Left(error) => Future.failed(PerformTestDesignerError(error))
case Right(value) => mapResultsToHttpResponse(value)
}
}
}
}
Expand All @@ -294,7 +364,10 @@ class ManagementResources(
process.isFragment,
testParametersRequest.sourceParameters
)
.flatMap(mapResultsToHttpResponse)
.flatMap {
case Left(error) => Future.failed(PerformTestDesignerError(error))
case Right(value) => mapResultsToHttpResponse(value)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ import pl.touk.nussknacker.ui.api.description.NodesApiEndpoints.Dtos.{
import pl.touk.nussknacker.ui.api.description.NodesApiEndpoints.Dtos.NodesError.BadRequestNodesError.{
InvalidNodeType,
MalformedTypingResult,
Serialization,
SourceCompilation,
TooManyCharactersGenerated,
TooManySamplesRequested,
UnsupportedSourcePreview
}
Expand All @@ -48,8 +48,8 @@ import pl.touk.nussknacker.ui.api.utils.ScenarioHttpServiceExtensions
import pl.touk.nussknacker.ui.process.ProcessService
import pl.touk.nussknacker.ui.process.processingtype.provider.ProcessingTypeDataProvider
import pl.touk.nussknacker.ui.process.repository.ProcessDBQueryRepository.ProcessNotFoundError
import pl.touk.nussknacker.ui.process.test.PreliminaryScenarioTestDataSerDe.SerializationError
import pl.touk.nussknacker.ui.process.test.ScenarioTestService
import pl.touk.nussknacker.ui.process.test.ScenarioTestService.SourceTestError
import pl.touk.nussknacker.ui.process.test.ScenarioTestService.SourceTestError._
import pl.touk.nussknacker.ui.security.api.{AuthManager, LoggedUser}
import pl.touk.nussknacker.ui.suggester.ExpressionSuggester
Expand Down Expand Up @@ -186,8 +186,11 @@ class NodesApiHttpService(
Future(Left(UnsupportedSourcePreview(nodeId)))
case Left(NoDataGeneratedError) =>
Future(Left(NoDataGenerated))
case Left(SerializationError(message)) =>
Future(Left(Serialization(message)))
case Left(ScenarioTestDataSerializationError(cause)) =>
Future(Left(cause match {
case SerializationError.TooManyCharactersGenerated(length, limit) =>
TooManyCharactersGenerated(length, limit)
}))
case Left(TooManySamplesRequestedError(maxSamples)) =>
Future(Left(TooManySamplesRequested(maxSamples)))
case Right(rawScenarioTestData) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ final case class EmptyDeploymentCommentSettingsError(message: String) extends Ex

@JsonCodec final case class IntervalTimeSettings(processes: Int, healthCheck: Int)

@JsonCodec final case class TestDataSettings(maxSamplesCount: Int, testDataMaxLength: Int, resultsMaxBytes: Int)
@JsonCodec final case class TestDataSettings(maxSamplesCount: Int, testDataMaxLength: Int, resultsMaxBytes: Long)

object TopTabType extends Enumeration {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package pl.touk.nussknacker.ui.api

object TestingApiErrorMessages {

object generatedTestData {
def requestedTooManySamplesToGenerate(maxSamples: Int) =
s"Too many samples requested. Please configure 'testDataSettings.maxSamplesCount' to increase the limit ($maxSamples)"

val couldNotProvideTestDataSample =
"Could not provide a sample of test data. Possible cause: no live sample data available"

val noSourcesWithTestDataGeneration = "No sources with test data generation available"

def tooManyCharacters(length: Int, limit: Int) =
s"Too many characters were found in the generated test data ($length). Please try to decrease the number of requested samples or configure 'testDataSettings.testDataMaxLength' to increase the limit ($limit)"
}

object passedTestData {
val empty = "Test data is empty"

def tooManyCharacters(length: Int, limit: Int) =
s"Test data has too many characters ($length). Please configure 'testDataSettings.testDataMaxLength' to increase the limit ($limit)"

def tooManySamples(count: Int, maxSamples: Int) =
s"Test data has too many samples ($count). Please configure 'testDataSettings.maxSamplesCount' to increase the limit ($maxSamples)"
}

case class problemInSample(private val recordIndex: Int) {

def parsingError(rawTestRecord: String): String = {
val trimmedRawTestRecord = rawTestRecord.take(300)
if (trimmedRawTestRecord.length < rawTestRecord.length) {
messageForSample(s"could not parse (shows fragment): '$trimmedRawTestRecord'")
} else {
messageForSample(s"could not parse: '$rawTestRecord'")
}
}

def missingSource(sourceId: String): String =
messageForSample(s"source with id '$sourceId' doesn't exist in the scenario")

def multipleSourcesRequired: String =
messageForSample("scenario has multiple sources, but got sample with unspecified source id")

private def messageForSample(message: String) =
s"Problem in sample ${recordIndex + 1} detected: $message"
}

def testResultsSizeExceeded(approxSizeInBytes: Long, maxBytes: Long) =
s"Test results size exceeded (approximate size in bytes: $approxSizeInBytes). Please configure 'testDataSettings.resultsMaxBytes' to increase the limit ($maxBytes)"

}
Loading

0 comments on commit 429a4dd

Please sign in to comment.