From 9a818b4ab1bf34603f5cdfb8079d714241c88485 Mon Sep 17 00:00:00 2001 From: Saw-jan Date: Wed, 25 Sep 2024 17:21:59 +0545 Subject: [PATCH 1/2] test: custom error message for json validation error --- tests/acceptance/bootstrap/FeatureContext.php | 96 ++++++++++++++++++- 1 file changed, 92 insertions(+), 4 deletions(-) diff --git a/tests/acceptance/bootstrap/FeatureContext.php b/tests/acceptance/bootstrap/FeatureContext.php index 9d6070bf6b..d7440f720e 100644 --- a/tests/acceptance/bootstrap/FeatureContext.php +++ b/tests/acceptance/bootstrap/FeatureContext.php @@ -34,7 +34,6 @@ use GuzzleHttp\Cookie\CookieJar; use Psr\Http\Message\ResponseInterface; use PHPUnit\Framework\Assert; use Swaggest\JsonSchema\Schema as JsonSchema; -use TestHelpers\OcsApiHelper; use Laminas\Ldap\Ldap; use TestHelpers\SetupHelper; use TestHelpers\HttpRequestHelper; @@ -44,6 +43,17 @@ use TestHelpers\GraphHelper; use TestHelpers\WebDavHelper; use TestHelpers\SettingsHelper; use TestHelpers\OcisConfigHelper; +use Swaggest\JsonSchema\InvalidValue as JsonSchemaException; +use Swaggest\JsonSchema\Exception\ArrayException; +use Swaggest\JsonSchema\Exception\ConstException; +use Swaggest\JsonSchema\Exception\ContentException; +use Swaggest\JsonSchema\Exception\EnumException; +use Swaggest\JsonSchema\Exception\LogicException; +use Swaggest\JsonSchema\Exception\NumericException; +use Swaggest\JsonSchema\Exception\ObjectException; +use Swaggest\JsonSchema\Exception\StringException; +use Swaggest\JsonSchema\Exception\TypeException; +use Swaggest\JsonSchema\Exception\Error as JsonSchemaError; require_once 'bootstrap.php'; @@ -1097,6 +1107,7 @@ class FeatureContext extends BehatVariablesContext { * @throws Exception */ public function validateSchemaObject(JsonSchema $schemaObj): void { + // TODO: check duplicate properties $this->checkInvalidValidator($schemaObj); if ($schemaObj->type && $schemaObj->type !== "object") { @@ -1240,9 +1251,86 @@ class FeatureContext extends BehatVariablesContext { * @throws Exception */ public function assertJsonDocumentMatchesSchema(object|array $json, object $schema): void { - $schema = JsonSchema::import($schema); - $this->validateSchemaRequirements($schema); - $schema->in($json); + try { + $schema = JsonSchema::import($schema); + $this->validateSchemaRequirements($schema); + $schema->in($json); + } catch (JsonSchemaException $e) { + // file_put_contents("test.log", var_export($e, true)); + $this->throwJsonSchemaException($e); + } + } + + /** + * @param JsonSchemaException $error + * + * @return array + */ + public function getJsonSchemaErrors(JsonSchemaException $error): array { + $errors = []; + if (\property_exists($error, "subErrors") && $error->subErrors) { + foreach ($error->subErrors as $subError) { + $errors = \array_merge($errors, $this->getJsonSchemaErrors($subError)); + } + } else { + $errors[] = $error; + } + return $errors; + } + + /** + * @param JsonSchemaException $e + * + * @return void + * @throws Exception + */ + public function throwJsonSchemaException(JsonSchemaException $e): void { + $errors = $this->getJsonSchemaErrors($e); + $messages = ["JSON Schema validation failed:"]; + foreach ($errors as $key => $error) { + $expected = $error->constraint; + $actual = $error->data; + $errorMessage = $error->error; + $schemaPointer = \str_replace("/", "->", \trim($error->getSchemaPointer(), "/")); + $dataPointer = \str_replace("/", "->", \trim($error->getDataPointer(), "/")); + + $pointer = \str_contains($schemaPointer, "additionalProperties") ? $dataPointer : $schemaPointer; + $message = ($key + 1) . ". "; + switch (true) { + case $error instanceof ArrayException: + case $error instanceof LogicException: + case $error instanceof NumericException: + case $error instanceof StringException: + case $error instanceof ContentException: + break; + case $error instanceof ConstException: + $errorMessage .= "\n\t Expected: $expected" + . "\n\t Received: $actual"; + break; + case $error instanceof EnumException: + $errorMessage .= "\n\t Expected (One of): " . \join(", ", $expected) + . "\n\t Received: $actual"; + break; + case $error instanceof ObjectException: + if (\str_starts_with($errorMessage, "Required property missing")) { + $properties = \join(", ", \array_keys((array)$actual)); + $pointer .= "->required"; + $errorMessage = "Required property missing: id" + . "\n\t Received: $properties"; + } + break; + case $error instanceof TypeException: + if (\in_array(\gettype($actual), ['object', 'array'])) { + $actual = \json_encode($actual, JSON_PRETTY_PRINT); + } + break; + default: + break; + } + $message .= "$pointer:\n\t - $errorMessage\n"; + $messages[] = $message; + } + Assert::fail(\join("\n", $messages)); } /** From ae9ef293af7e09773b61dea021f6cba86ea84309 Mon Sep 17 00:00:00 2001 From: Saw-jan Date: Thu, 26 Sep 2024 12:17:54 +0545 Subject: [PATCH 2/2] test: only one error per path --- tests/acceptance/bootstrap/FeatureContext.php | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/tests/acceptance/bootstrap/FeatureContext.php b/tests/acceptance/bootstrap/FeatureContext.php index d7440f720e..bce949a068 100644 --- a/tests/acceptance/bootstrap/FeatureContext.php +++ b/tests/acceptance/bootstrap/FeatureContext.php @@ -1107,7 +1107,6 @@ class FeatureContext extends BehatVariablesContext { * @throws Exception */ public function validateSchemaObject(JsonSchema $schemaObj): void { - // TODO: check duplicate properties $this->checkInvalidValidator($schemaObj); if ($schemaObj->type && $schemaObj->type !== "object") { @@ -1256,7 +1255,6 @@ class FeatureContext extends BehatVariablesContext { $this->validateSchemaRequirements($schema); $schema->in($json); } catch (JsonSchemaException $e) { - // file_put_contents("test.log", var_export($e, true)); $this->throwJsonSchemaException($e); } } @@ -1287,15 +1285,23 @@ class FeatureContext extends BehatVariablesContext { public function throwJsonSchemaException(JsonSchemaException $e): void { $errors = $this->getJsonSchemaErrors($e); $messages = ["JSON Schema validation failed:"]; - foreach ($errors as $key => $error) { + + $previousPointer = ''; + $errorCount = 0; + foreach ($errors as $error) { $expected = $error->constraint; $actual = $error->data; $errorMessage = $error->error; - $schemaPointer = \str_replace("/", "->", \trim($error->getSchemaPointer(), "/")); - $dataPointer = \str_replace("/", "->", \trim($error->getDataPointer(), "/")); + $schemaPointer = \str_replace("/", ".", \trim($error->getSchemaPointer(), "/")); + $dataPointer = \str_replace("/", ".", \trim($error->getDataPointer(), "/")); $pointer = \str_contains($schemaPointer, "additionalProperties") ? $dataPointer : $schemaPointer; - $message = ($key + 1) . ". "; + if ($pointer === $previousPointer) { + continue; + } + $previousPointer = $pointer; + + $message = ++$errorCount . ". "; switch (true) { case $error instanceof ArrayException: case $error instanceof LogicException: