Skip to content

feat: update to latest json schema test suite #821

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jun 24, 2025
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
},
"require-dev": {
"friendsofphp/php-cs-fixer": "3.3.0",
"json-schema/json-schema-test-suite": "1.2.0",
"json-schema/json-schema-test-suite": "^23.2",
"phpunit/phpunit": "^8.5",
"phpspec/prophecy": "^1.19",
"phpstan/phpstan": "^1.12",
Expand All @@ -59,11 +59,11 @@
"type": "package",
"package": {
"name": "json-schema/json-schema-test-suite",
"version": "1.2.0",
"version": "23.2.0",
"source": {
"type": "git",
"url": "https://github.com/json-schema/JSON-Schema-Test-Suite",
"reference": "1.2.0"
"reference": "23.2.0"
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,8 @@
<directory>./src/JsonSchema/</directory>
</whitelist>
</filter>

<php>
<ini name="memory_limit" value="-1"/>
</php>
</phpunit>
48 changes: 34 additions & 14 deletions tests/Constraints/BaseTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ abstract class BaseTestCase extends VeryBaseTestCase
/**
* @dataProvider getInvalidTests
*
* @param int-mask-of<Constraint::CHECK_MODE_*> $checkMode
* @param ?int-mask-of<Constraint::CHECK_MODE_*> $checkMode
*/
public function testInvalidCases(string $input, string $schema, ?int $checkMode = Constraint::CHECK_MODE_NORMAL, array $errors = []): void
{
Expand All @@ -28,8 +28,9 @@ public function testInvalidCases(string $input, string $schema, ?int $checkMode
$checkMode |= Constraint::CHECK_MODE_VALIDATE_SCHEMA;
}

$schemaStorage = new SchemaStorage($this->getUriRetrieverMock(json_decode($schema, false)));
$schema = $schemaStorage->getSchema('http://www.my-domain.com/schema.json');
$schema = json_decode($schema, false);
$schemaStorage = new SchemaStorage($this->getUriRetrieverMock($schema));
$schema = $schemaStorage->getSchema($schema->id ?? 'http://www.my-domain.com/schema.json');
if (is_object($schema) && !isset($schema->{'$schema'})) {
$schema->{'$schema'} = $this->schemaSpec;
}
Expand All @@ -38,7 +39,7 @@ public function testInvalidCases(string $input, string $schema, ?int $checkMode
$checkValue = json_decode($input, false);
$errorMask = $validator->validate($checkValue, $schema);

$this->assertTrue((bool) ($errorMask & Validator::ERROR_DOCUMENT_VALIDATION));
$this->assertTrue((bool) ($errorMask & Validator::ERROR_DOCUMENT_VALIDATION), 'Document is invalid');
$this->assertGreaterThan(0, $validator->numErrors());

if ([] !== $errors) {
Expand All @@ -49,8 +50,10 @@ public function testInvalidCases(string $input, string $schema, ?int $checkMode

/**
* @dataProvider getInvalidForAssocTests
*
* @param ?int-mask-of<Constraint::CHECK_MODE_*> $checkMode
*/
public function testInvalidCasesUsingAssoc($input, $schema, $checkMode = Constraint::CHECK_MODE_TYPE_CAST, $errors = []): void
public function testInvalidCasesUsingAssoc(string $input, string $schema, ?int $checkMode = Constraint::CHECK_MODE_TYPE_CAST, array $errors = []): void
{
$checkMode = $checkMode ?? Constraint::CHECK_MODE_TYPE_CAST;
if ($this->validateSchema) {
Expand All @@ -60,8 +63,9 @@ public function testInvalidCasesUsingAssoc($input, $schema, $checkMode = Constra
$this->markTestSkipped('Test indicates that it is not for "CHECK_MODE_TYPE_CAST"');
}

$schemaStorage = new SchemaStorage($this->getUriRetrieverMock(json_decode($schema)));
$schema = $schemaStorage->getSchema('http://www.my-domain.com/schema.json');
$schema = json_decode($schema, false);
$schemaStorage = new SchemaStorage($this->getUriRetrieverMock($schema));
$schema = $schemaStorage->getSchema($schema->id ?? 'http://www.my-domain.com/schema.json');
if (is_object($schema) && !isset($schema->{'$schema'})) {
$schema->{'$schema'} = $this->schemaSpec;
}
Expand All @@ -81,14 +85,18 @@ public function testInvalidCasesUsingAssoc($input, $schema, $checkMode = Constra

/**
* @dataProvider getValidTests
*
* @param ?int-mask-of<Constraint::CHECK_MODE_*> $checkMode
*/
public function testValidCases($input, $schema, $checkMode = Constraint::CHECK_MODE_NORMAL): void
public function testValidCases(string $input, string $schema, ?int $checkMode = Constraint::CHECK_MODE_NORMAL): void
{
if ($this->validateSchema) {
$checkMode |= Constraint::CHECK_MODE_VALIDATE_SCHEMA;
}
$schemaStorage = new SchemaStorage($this->getUriRetrieverMock(json_decode($schema, false)));
$schema = $schemaStorage->getSchema('http://www.my-domain.com/schema.json');

$schema = json_decode($schema, false);
$schemaStorage = new SchemaStorage($this->getUriRetrieverMock($schema));
$schema = $schemaStorage->getSchema($schema->id ?? 'http://www.my-domain.com/schema.json');
if (is_object($schema) && !isset($schema->{'$schema'})) {
$schema->{'$schema'} = $this->schemaSpec;
}
Expand All @@ -103,8 +111,10 @@ public function testValidCases($input, $schema, $checkMode = Constraint::CHECK_M

/**
* @dataProvider getValidForAssocTests
*
* @param ?int-mask-of<Constraint::CHECK_MODE_*> $checkMode
*/
public function testValidCasesUsingAssoc($input, $schema, $checkMode = Constraint::CHECK_MODE_TYPE_CAST): void
public function testValidCasesUsingAssoc(string $input, string $schema, ?int $checkMode = Constraint::CHECK_MODE_TYPE_CAST): void
{
if ($this->validateSchema) {
$checkMode |= Constraint::CHECK_MODE_VALIDATE_SCHEMA;
Expand All @@ -113,9 +123,9 @@ public function testValidCasesUsingAssoc($input, $schema, $checkMode = Constrain
$this->markTestSkipped('Test indicates that it is not for "CHECK_MODE_TYPE_CAST"');
}

$schema = json_decode($schema);
$schema = json_decode($schema, false);
$schemaStorage = new SchemaStorage($this->getUriRetrieverMock($schema), new UriResolver());
$schema = $schemaStorage->getSchema('http://www.my-domain.com/schema.json');
$schema = $schemaStorage->getSchema($schema->id ?? 'http://www.my-domain.com/schema.json');
if (is_object($schema) && !isset($schema->{'$schema'})) {
$schema->{'$schema'} = $this->schemaSpec;
}
Expand All @@ -124,7 +134,7 @@ public function testValidCasesUsingAssoc($input, $schema, $checkMode = Constrain
$validator = new Validator(new Factory($schemaStorage, null, $checkMode));

$errorMask = $validator->validate($value, $schema);
$this->assertEquals(0, $errorMask);
$this->assertEquals(0, $errorMask, $this->validatorErrorsToString($validator));
$this->assertTrue($validator->isValid(), print_r($validator->getErrors(), true));
}

Expand All @@ -141,4 +151,14 @@ public function getInvalidForAssocTests(): Generator
{
yield from $this->getInvalidTests();
}

private function validatorErrorsToString(Validator $validator): string
{
return implode(
', ',
array_map(
static function (array $error) { return $error['message']; }, $validator->getErrors()
)
);
}
}
42 changes: 18 additions & 24 deletions tests/Constraints/NumberAndIntegerTypesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,37 +12,35 @@ class NumberAndIntegerTypesTest extends BaseTestCase
public function getInvalidTests(): \Generator
{
yield [
'{
"integer": 1.4
}',
'{
'input' => '{ "integer": 1.4 }',
'schema' => '{
"type":"object",
"properties":{
"integer":{"type":"integer"}
}
}'
];
yield [
'{"integer": 1.001}',
'{
'input' => '{"integer": 1.001}',
'schema' => '{
"type": "object",
"properties": {
"integer": {"type": "integer"}
}
}'
];
yield [
'{"integer": true}',
'{
'input' => '{"integer": true}',
'schema' => '{
"type": "object",
"properties": {
"integer": {"type": "integer"}
}
}'
];
yield [
'{"number": "x"}',
'{
'input' => '{"number": "x"}',
'schema' => '{
"type": "object",
"properties": {
"number": {"type": "number"}
Expand All @@ -54,39 +52,35 @@ public function getInvalidTests(): \Generator
public function getValidTests(): \Generator
{
yield [
'{
"integer": 1
}',
'{
'input' => '{ "integer": 1 }',
'schema' => '{
"type":"object",
"properties":{
"integer":{"type":"integer"}
}
}'
];
yield [
'{
"number": 1.4
}',
'{
'input' => '{ "number": 1.4 }',
'schema' => '{
"type":"object",
"properties":{
"number":{"type":"number"}
}
}'
];
yield [
'{"number": 1e5}',
'{
'input' => '{"number": 1e5}',
'schema' => '{
"type": "object",
"properties": {
"number": {"type": "number"}
}
}'
];
yield [
'{"number": 1}',
'{
'input' => '{"number": 1}',
'schema' => '{
"type": "object",
"properties": {
"number": {"type": "number"}
Expand All @@ -95,8 +89,8 @@ public function getValidTests(): \Generator
}'
];
yield [
'{"number": -49.89}',
'{
'input' => '{"number": -49.89}',
'schema' => '{
"type": "object",
"properties": {
"number": {
Expand Down
7 changes: 6 additions & 1 deletion tests/Constraints/VeryBaseTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ abstract class VeryBaseTestCase extends TestCase
protected function getUriRetrieverMock(?object $schema): object
{
$uriRetriever = $this->prophesize(UriRetrieverInterface::class);
$uriRetriever->retrieve('http://www.my-domain.com/schema.json')
$uriRetriever->retrieve($schema->id ?? 'http://www.my-domain.com/schema.json')
->willReturn($schema)
->shouldBeCalled();

Expand Down Expand Up @@ -71,4 +71,9 @@ private function readAndJsonDecodeFile(string $file): stdClass

return json_decode(file_get_contents($file), false);
}

protected function is32Bit(): bool
{
return PHP_INT_SIZE === 4;
}
}
15 changes: 15 additions & 0 deletions tests/Drafts/Draft3Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,20 @@ protected function getFilePaths(): array
];
}

public function getInvalidTests(): \Generator
{
$skip = [
'ref.json / $ref prevents a sibling id from changing the base uri / $ref resolves to /definitions/base_foo, data does not validate'
];

foreach (parent::getInvalidTests() as $name => $testcase) {
if (in_array($name, $skip, true)) {
continue;
}
yield $name => $testcase;
}
}

public function getInvalidForAssocTests(): \Generator
{
$skip = [
Expand Down Expand Up @@ -113,6 +127,7 @@ protected function getSkippedTests(): array
return [
// Optional
'bignum.json',
'ecmascript-regex.json',
'format.json',
'jsregex.json',
'zeroTerminatedFloats.json'
Expand Down
56 changes: 56 additions & 0 deletions tests/Drafts/Draft4Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,33 @@ protected function getFilePaths(): array
];
}

public function getInvalidTests(): \Generator
{
$skip = [
'id.json / id inside an enum is not a real identifier / no match on enum or $ref to id',
'ref.json / $ref prevents a sibling id from changing the base uri / $ref resolves to /definitions/base_foo, data does not validate',
'ref.json / Recursive references between schemas / invalid tree',
'ref.json / refs with quote / object with strings is invalid',
'ref.json / Location-independent identifier / mismatch',
'ref.json / Location-independent identifier with base URI change in subschema / mismatch',
'ref.json / empty tokens in $ref json-pointer / non-number is invalid',
'ref.json / id must be resolved against nearest parent, not just immediate parent / non-number is invalid',
'refRemote.json / Location-independent identifier in remote ref / string is invalid',
'refRemote.json / base URI change - change folder / string is invalid'
];

foreach (parent::getInvalidTests() as $name => $testcase) {
if (in_array($name, $skip, true)) {
continue;
}
yield $name => $testcase;
}
}

public function getInvalidForAssocTests(): \Generator
{
$skip = [
'ref.json / Recursive references between schemas / valid tree',
'type.json / object type matches objects / an array is not an object',
'type.json / array type matches arrays / an object is not an array',
];
Expand All @@ -35,9 +59,39 @@ public function getInvalidForAssocTests(): \Generator
}
}

public function getValidTests(): \Generator
{
$skip = [
'ref.json / $ref prevents a sibling id from changing the base uri / $ref resolves to /definitions/base_foo, data validates',
'ref.json / Recursive references between schemas / valid tree',
'ref.json / refs with quote / object with numbers is valid',
'ref.json / Location-independent identifier / match',
'ref.json / Location-independent identifier with base URI change in subschema / match',
'ref.json / empty tokens in $ref json-pointer / number is valid',
'ref.json / naive replacement of $ref with its destination is not correct / match the enum exactly',
'ref.json / id must be resolved against nearest parent, not just immediate parent / number is valid',
'refRemote.json / Location-independent identifier in remote ref / integer is valid',
'refRemote.json / base URI change - change folder / number is valid',
];

if ($this->is32Bit()) {
$skip[] = 'multipleOf.json / small multiple of large integer / any integer is a multiple of 1e-8'; // Test case contains a number which doesn't fit in 32 bits
}

foreach (parent::getValidTests() as $name => $testcase) {
if (in_array($name, $skip, true)) {
continue;
}
yield $name => $testcase;
}
}

public function getValidForAssocTests(): \Generator
{
$skip = [
'minProperties.json / minProperties validation / ignores arrays',
'required.json / required properties whose names are Javascript object property names / ignores arrays',
'required.json / required validation / ignores arrays',
'type.json / object type matches objects / an array is not an object',
'type.json / array type matches arrays / an object is not an array',
];
Expand All @@ -58,7 +112,9 @@ protected function getSkippedTests(): array
return [
// Optional
'bignum.json',
'ecmascript-regex.json',
'format.json',
'float-overflow.json',
'zeroTerminatedFloats.json',
// Required
'not.json' // only one test case failing
Expand Down
Loading