diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 528e26c..4bccff1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,11 +1,11 @@ name: Test -on: [pull_request] +on: [push, pull_request] jobs: psalm: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Psalm uses: docker://vimeo/psalm-github-actions @@ -51,16 +51,16 @@ jobs: strategy: matrix: php: - - 7.4 - - 8.0 + - 8.2 + - 8.3 include: - - php: 7.4 - phpunit: 9.5.0 - - php: 8.0 - phpunit: 9.5.0 + - php: 8.2 + phpunit: 10 + - php: 8.3 + phpunit: 10 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Cache Composer dependencies uses: actions/cache@v2 @@ -72,7 +72,13 @@ jobs: with: php_version: ${{ matrix.php }} - - uses: php-actions/phpunit@v9 + - uses: php-actions/phpunit@v3 with: php_version: ${{ matrix.php }} version: ${{ matrix.phpunit }} + php_extensions: xdebug + coverage_text: true + bootstrap: vendor/autoload.php + args: --coverage-filter src tests + env: + XDEBUG_MODE: coverage diff --git a/README.md b/README.md index 3bdf443..a8e14c9 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ class MyTestCase extends \PHPUnit_Framework_TestCase ### Class -In case you don't want to use the `trait` you can use the provided class wich extends from `\PHPUnit_Framework_TestCase`. +In case you don't want to use the `trait` you can use the provided class which extends from `\PHPUnit_Framework_TestCase`. You can either extend your test case or use the static methods like below. ```php diff --git a/composer.json b/composer.json index 9bd4764..082bdb2 100644 --- a/composer.json +++ b/composer.json @@ -14,12 +14,12 @@ "minimum-stability": "stable", "require": { "php": "^7.4|^8.0", - "justinrainbow/json-schema": "^5.0", + "justinrainbow/json-schema": "^6.0", "mtdowling/jmespath.php": "^2.3", "ext-json": "*" }, "require-dev": { - "phpunit/phpunit": "^9", + "phpunit/phpunit": "^10", "codacy/coverage": "dev-master", "symfony/http-foundation": "^2.8|^3.0|^5.0" }, diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 111d4a7..bbe3525 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,22 +1,18 @@ - - - - - tests/ - - - - - - src/ - - - - - + + + + tests/ + + + + + + + + + + src/ + + diff --git a/src/Assert.php b/src/Assert.php index 49bc458..513bb49 100644 --- a/src/Assert.php +++ b/src/Assert.php @@ -63,7 +63,7 @@ public static function assertJsonMatchesSchema($content, ?string $schema = null) $message = '- Property: %s, Constraint: %s, Message: %s'; $messages = array_map(function ($exception) use ($message) { - return sprintf($message, $exception['property'], $exception['constraint'], $exception['message']); + return sprintf($message, $exception['property'], $exception['constraint']['name'], $exception['message']); }, $validator->getErrors()); $messages[] = '- Response: '.json_encode($content); diff --git a/tests/AssertClassTest.php b/tests/AssertClassTest.php index adcf42f..541e8c1 100644 --- a/tests/AssertClassTest.php +++ b/tests/AssertClassTest.php @@ -18,6 +18,6 @@ class AssertClassTest extends TestCase { public function testClassInstance() { - static::assertInstanceOf('EnricoStahn\JsonAssert\AssertClass', new AssertClass()); + static::assertInstanceOf('EnricoStahn\JsonAssert\AssertClass', new AssertClass('AssertClassTest')); } } diff --git a/tests/AssertTraitTest.php b/tests/AssertTraitTest.php index 8885cd2..abbaab0 100644 --- a/tests/AssertTraitTest.php +++ b/tests/AssertTraitTest.php @@ -22,11 +22,164 @@ class AssertTraitTest extends TestCase * * @see https://github.com/estahn/phpunit-json-assertions/wiki/assertJsonMatchesSchema */ + public function testAssertJsonMatchesSchemaSimpleDraft6() + { + $content = json_decode(file_get_contents(Utils::getJsonPath('assertJsonMatchesSchema_simple_draft6.json'))); + + AssertTraitImpl::assertJsonMatchesSchema( + $content, + Utils::getSchemaPath('assertJsonMatchesSchema_draft6.schema.json') + ); + } + + /** + * @testWith + * ["{\"created_at\": \"2016-01-01T12:00:00Z\"}", true] + * ["{\"created_at\": \"2016/01/01\"}", false] + */ + public function testAssertJsonMatchesSchemaDraft6DateTime($json, $pass) + { + if (!$pass) { + $this->expectException(ExpectationFailedException::class); + } + + $content = json_decode($json); + + AssertTraitImpl::assertJsonMatchesSchema( + $content, + Utils::getSchemaPath('assertJsonMatchesSchema_draft6.schema.json') + ); + } + + /** + * @testWith + * ["{\"status\": \"active\", \"created_at\": \"2016-01-01T12:00:00Z\"}", true] + * ["{\"status\": \"completed\", \"created_at\": \"2016-01-01T12:00:00Z\"}", true] + * ["{\"status\": \"deleted\", \"created_at\": \"2016-01-01T12:00:00Z\"}", false] + */ + public function testAssertJsonMatchesSchemaDraft6EnumAndNot($json, $pass) + { + if (!$pass) { + $this->expectException(ExpectationFailedException::class); + } + + $content = json_decode($json); + + AssertTraitImpl::assertJsonMatchesSchema( + $content, + Utils::getSchemaPath('assertJsonMatchesSchema_draft6.schema.json') + ); + } + + /** + * @testWith + * ["{\"status\": \"active\", \"created_at\": \"2016-01-01T12:00:00Z\"}", true] + * ["{\"status\": \"active\"}", false] + */ + public function testAssertJsonMatchesSchemaDraft6Dependency($json, $pass) + { + if (!$pass) { + $this->expectException(ExpectationFailedException::class); + } + + $content = json_decode($json); + + AssertTraitImpl::assertJsonMatchesSchema( + $content, + Utils::getSchemaPath('assertJsonMatchesSchema_draft6.schema.json') + ); + } + + /** + * @testWith + * ["{\"id\": 2}", true] + * ["{\"id\": 1}", false] + * ["{\"id\": 0}", false] + */ + public function testAssertJsonMatchesSchemaDraft6ExclusiveMinimum($json, $pass) + { + if (!$pass) { + $this->expectException(ExpectationFailedException::class); + } + + $content = json_decode($json); + + AssertTraitImpl::assertJsonMatchesSchema( + $content, + Utils::getSchemaPath('assertJsonMatchesSchema_draft6.schema.json') + ); + } + + /** + * @testWith + * ["{\"title\": \"A brief description\"}", true] + * ["{\"title\": \"A description that is too long\"}", false] + * ["{\"title\": \"A\"}", false] + */ + public function testAssertJsonMatchesSchemaDraft6MaxMinLength($json, $pass) + { + if (!$pass) { + $this->expectException(ExpectationFailedException::class); + } + + $content = json_decode($json); + + AssertTraitImpl::assertJsonMatchesSchema( + $content, + Utils::getSchemaPath('assertJsonMatchesSchema_draft6.schema.json') + ); + } + + /** + * @testWith + * ["{\"invalid_name\": \"value\"}", false] + */ + public function testAssertJsonMatchesSchemaDraft6AdditionalProperties($json, $pass) + { + if (!$pass) { + $this->expectException(ExpectationFailedException::class); + } + + $content = json_decode($json); + + AssertTraitImpl::assertJsonMatchesSchema( + $content, + Utils::getSchemaPath('assertJsonMatchesSchema_draft6.schema.json') + ); + } + + /** + * @testWith + * ["{\"status\": \"completed\", \"completed_at\": \"2020-01-01T12:00:00Z\", \"created_at\": \"2020-01-01T12:00:00Z\"}", true] + * ["{\"status\": \"completed\", \"created_at\": \"2020-01-01T12:00:00Z\"}", false] + * ["{\"status\": \"pending\", \"expected_completion\": \"2020-01-01T12:00:00Z\", \"created_at\": \"2020-01-01T12:00:00Z\"}", true] + * ["{\"status\": \"pending\", \"created_at\": \"2020-01-01T12:00:00Z\"}", false] + * ["{\"status\": \"active\", \"created_at\": \"2020-01-01T12:00:00Z\"}", true] + */ + public function testAssertJsonMatchesSchemaDraft6Conditional($json, $pass) + { + $this->markTestSkipped('Conditional validation is not supported by the current implementation.'); + + if (!$pass) { + $this->expectException(ExpectationFailedException::class); + } + + $content = json_decode($json); + + AssertTraitImpl::assertJsonMatchesSchema( + $content, + Utils::getSchemaPath('assertJsonMatchesSchema_draft6.schema.json') + ); + } + public function testAssertJsonMatchesSchemaSimple() { $content = json_decode(file_get_contents(Utils::getJsonPath('assertJsonMatchesSchema_simple.json'))); - AssertTraitImpl::assertJsonMatchesSchema($content, Utils::getSchemaPath('assertJsonMatchesSchema_simple.schema.json')); + AssertTraitImpl::assertJsonMatchesSchema( + $content, + Utils::getSchemaPath('assertJsonMatchesSchema_simple.schema.json') + ); } public function testAssertJsonMatchesSchema() @@ -61,7 +214,10 @@ public function testAssertJsonMatchesSchemaFailMessage() try { AssertTraitImpl::assertJsonMatchesSchema($content, Utils::getSchemaPath('test.schema.json')); } catch (ExpectationFailedException $exception) { - self::assertStringContainsString('- Property: foo, Constraint: type, Message: String value found, but an integer is required', $exception->getMessage()); + self::assertStringContainsString( + '- Property: foo, Constraint: type, Message: String value found, but an integer is required', + $exception->getMessage() + ); self::assertStringContainsString('- Response: {"foo":"123"}', $exception->getMessage()); } @@ -111,7 +267,7 @@ public function testAssertJsonValueEquals(string $expression, $value) public function testAssertWithSchemaStore() { - $obj = new AssertTraitImpl(); + $obj = new AssertTraitImpl('testAssertWithSchemaStore'); $obj->setUp(); $schemaStore = $obj->testWithSchemaStore('foobar', (object) ['type' => 'string']); @@ -120,7 +276,7 @@ public function testAssertWithSchemaStore() self::assertEquals($schemaStore->getSchema('foobar'), (object) ['type' => 'string']); } - public function assertJsonValueEqualsProvider(): array + public static function assertJsonValueEqualsProvider(): array { return [ ['foo', '123'], @@ -144,7 +300,7 @@ public function testGetJsonObject($expected, $actual) self::assertEquals($expected, AssertTraitImpl::getJsonObject($actual)); } - public function jsonObjectProvider(): array + public static function jsonObjectProvider(): array { return [ [[], []], diff --git a/tests/json/assertJsonMatchesSchema_simple_draft6.json b/tests/json/assertJsonMatchesSchema_simple_draft6.json new file mode 100644 index 0000000..70f3e1c --- /dev/null +++ b/tests/json/assertJsonMatchesSchema_simple_draft6.json @@ -0,0 +1,11 @@ +{ + "id": 33, + "title": "Sample Title", + "status": "active", + "created_at": "2009-03-24T16:24:32Z", + "tags": [ + "foo", + "bar" + ], + "meta_description": "A brief description." +} diff --git a/tests/schemas/assertJsonMatchesSchema_draft6.schema.json b/tests/schemas/assertJsonMatchesSchema_draft6.schema.json new file mode 100644 index 0000000..f829b00 --- /dev/null +++ b/tests/schemas/assertJsonMatchesSchema_draft6.schema.json @@ -0,0 +1,81 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "type": "object", + "additionalProperties": false, + "minProperties": 1, + "maxProperties": 7, + "properties": { + "id": { + "type": "integer", + "minimum": 1, + "exclusiveMinimum": 1, + "multipleOf": 1 + }, + "title": { + "type": "string", + "examples": ["Sample Title", "Another Title"], + "minLength": 2, + "maxLength": 20 + }, + "status": { + "type": "string", + "enum": ["active", "pending", "completed"], + "not": { + "enum": ["inactive", "deleted"] + } + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "completed_at": { + "type": "string", + "format": "date-time" + }, + "expected_completion": { + "type": "string", + "format": "date-time" + }, + "tags": { + "type": "array", + "minItems": 1, + "maxItems": 10, + "items": { + "type": "string" + }, + "uniqueItems": true, + "contains": { "const": "foo" } + }, + "meta_description": { + "type": "string" + } + }, + "dependencies": { + "status": ["created_at"] + }, + "patternProperties": { + "^meta_": { + "type": "string", + "minLength": 5 + } + }, + "propertyNames": { + "pattern": "^[a-z][a-z0-9_]*$", + "maxLength": 10 + }, + + "if": { + "properties": { "status": { "const": "completed" } } + }, + "then": { + "required": ["completed_at"] + }, + "else": { + "if": { + "properties": { "status": { "const": "pending" } } + }, + "then": { + "required": ["expected_completion"] + } + } +}