From 8a4a09bfeb7050d5d8482c39b398ca1f6304330d Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Sat, 16 Nov 2019 11:51:27 +1030 Subject: [PATCH] More tests --- src/Handler/Delete.php | 6 +- tests/AbstractTestCase.php | 1 - tests/MockAdapter.php | 7 +- tests/feature/CountabilityTest.php | 46 +++++++- tests/feature/CreateTest.php | 163 +++++++++++++++++++++++--- tests/feature/DeleteTest.php | 145 ++++++++++++++++++++--- tests/feature/FieldVisibilityTest.php | 2 +- tests/feature/MetaTest.php | 43 +++++-- tests/feature/ScopesTest.php | 1 - 9 files changed, 360 insertions(+), 54 deletions(-) diff --git a/src/Handler/Delete.php b/src/Handler/Delete.php index f44d9ae..8452f9a 100644 --- a/src/Handler/Delete.php +++ b/src/Handler/Delete.php @@ -32,7 +32,11 @@ class Delete implements RequestHandlerInterface run_callbacks($schema->getListeners('deleting'), [$this->model, $request]); - $this->resource->getAdapter()->delete($this->model); + if ($deleter = $this->resource->getSchema()->getDelete()) { + $deleter($this->model, $request); + } else { + $this->resource->getAdapter()->delete($this->model); + } run_callbacks($schema->getListeners('deleted'), [$this->model, $request]); diff --git a/tests/AbstractTestCase.php b/tests/AbstractTestCase.php index a4d4054..11c4602 100644 --- a/tests/AbstractTestCase.php +++ b/tests/AbstractTestCase.php @@ -13,7 +13,6 @@ namespace Tobyz\Tests\JsonApiServer; use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts; use PHPUnit\Framework\TestCase; -use Psr\Http\Message\ResponseInterface; use Zend\Diactoros\ServerRequest; use Zend\Diactoros\Uri; diff --git a/tests/MockAdapter.php b/tests/MockAdapter.php index 4500bfa..eb094d4 100644 --- a/tests/MockAdapter.php +++ b/tests/MockAdapter.php @@ -2,6 +2,7 @@ namespace Tobyz\Tests\JsonApiServer; +use Closure; use Tobyz\JsonApiServer\Adapter\AdapterInterface; use Tobyz\JsonApiServer\Schema\Attribute; use Tobyz\JsonApiServer\Schema\Field; @@ -52,12 +53,12 @@ class MockAdapter implements AdapterInterface return $model->{$this->getProperty($attribute)} ?? 'default'; } - public function getHasOne($model, HasOne $relationship) + public function getHasOne($model, HasOne $relationship, array $fields = null) { return $model->{$this->getProperty($relationship)} ?? null; } - public function getHasMany($model, HasMany $relationship): array + public function getHasMany($model, HasMany $relationship, array $fields = null): array { return $model->{$this->getProperty($relationship)} ?? []; } @@ -121,7 +122,7 @@ class MockAdapter implements AdapterInterface $query->paginate[] = [$limit, $offset]; } - public function load(array $models, array $relationships): void + public function load(array $models, array $relationships, Closure $scope): void { foreach ($models as $model) { $model->load[] = $relationships; diff --git a/tests/feature/CountabilityTest.php b/tests/feature/CountabilityTest.php index 4969d39..cf6217f 100644 --- a/tests/feature/CountabilityTest.php +++ b/tests/feature/CountabilityTest.php @@ -12,6 +12,7 @@ namespace Tobyz\Tests\JsonApiServer\feature; use Tobyz\JsonApiServer\JsonApi; +use Tobyz\JsonApiServer\Schema\Type; use Tobyz\Tests\JsonApiServer\AbstractTestCase; use Tobyz\Tests\JsonApiServer\MockAdapter; @@ -31,21 +32,58 @@ class CountabilityTest extends AbstractTestCase { $this->api = new JsonApi('http://example.com'); - $this->adapter = new MockAdapter(); + $models = []; + for ($i = 1; $i <= 100; $i++) { + $models[] = (object) ['type' => 'users', 'id' => $i]; + } + + $this->adapter = new MockAdapter($models, 'users'); } public function test_total_number_of_resources_and_last_pagination_link_is_included_by_default() { - $this->markTestIncomplete(); + $this->api->resource('users', $this->adapter); + + $response = $this->api->handle( + $this->buildRequest('GET', '/users') + ); + + $document = json_decode($response->getBody(), true); + + $this->assertArrayHasKey('last', $document['links'] ?? []); + $this->assertEquals(100, $document['meta']['total'] ?? null); } public function test_types_can_be_made_uncountable() { - $this->markTestIncomplete(); + $this->api->resource('users', $this->adapter, function (Type $type) { + $type->uncountable(); + }); + + $response = $this->api->handle( + $this->buildRequest('GET', '/users') + ); + + $document = json_decode($response->getBody(), true); + + $this->assertArrayNotHasKey('last', $document['links'] ?? []); + $this->assertArrayNotHasKey('total', $document['meta'] ?? []); } public function test_types_can_be_made_countable() { - $this->markTestIncomplete(); + $this->api->resource('users', $this->adapter, function (Type $type) { + $type->uncountable(); + $type->countable(); + }); + + $response = $this->api->handle( + $this->buildRequest('GET', '/users') + ); + + $document = json_decode($response->getBody(), true); + + $this->assertArrayHasKey('last', $document['links'] ?? []); + $this->assertEquals(100, $document['meta']['total'] ?? null); } } diff --git a/tests/feature/CreateTest.php b/tests/feature/CreateTest.php index db90683..0c4ff11 100644 --- a/tests/feature/CreateTest.php +++ b/tests/feature/CreateTest.php @@ -11,7 +11,11 @@ namespace Tobyz\Tests\JsonApiServer\feature; +use Psr\Http\Message\ServerRequestInterface; +use Tobyz\JsonApiServer\Adapter\AdapterInterface; +use Tobyz\JsonApiServer\Exception\ForbiddenException; use Tobyz\JsonApiServer\JsonApi; +use Tobyz\JsonApiServer\Schema\Type; use Tobyz\Tests\JsonApiServer\AbstractTestCase; use Tobyz\Tests\JsonApiServer\MockAdapter; @@ -22,58 +26,181 @@ class CreateTest extends AbstractTestCase */ private $api; - /** - * @var MockAdapter - */ - private $adapter; - public function setUp(): void { $this->api = new JsonApi('http://example.com'); + } - $this->adapter = new MockAdapter(); + protected function createResource(array $data = []) + { + return $this->api->handle( + $this->buildRequest('POST', '/users') + ->withParsedBody([ + 'data' => array_merge([ + 'type' => 'users', + 'id' => '1', + ], $data) + ]) + ); } public function test_resources_are_not_creatable_by_default() { - $this->markTestIncomplete(); + $this->api->resource('users', new MockAdapter()); + + $this->expectException(ForbiddenException::class); + + $this->createResource(); } public function test_resource_creation_can_be_explicitly_enabled() { - $this->markTestIncomplete(); + $this->api->resource('users', new MockAdapter(), function (Type $type) { + $type->creatable(); + }); + + $response = $this->createResource(); + + $this->assertEquals(201, $response->getStatusCode()); } public function test_resource_creation_can_be_conditionally_enabled() { - $this->markTestIncomplete(); + $this->api->resource('users', new MockAdapter(), function (Type $type) { + $type->creatable(function () { + return true; + }); + }); + + $response = $this->createResource(); + + $this->assertEquals(201, $response->getStatusCode()); } public function test_resource_creation_can_be_explicitly_disabled() { - $this->markTestIncomplete(); + $this->api->resource('users', new MockAdapter(), function (Type $type) { + $type->notCreatable(); + }); + + $this->expectException(ForbiddenException::class); + + $this->createResource(); } public function test_resource_creation_can_be_conditionally_disabled() { - $this->markTestIncomplete(); + $this->api->resource('users', new MockAdapter(), function (Type $type) { + $type->creatable(function () { + return false; + }); + }); + + $this->expectException(ForbiddenException::class); + + $this->createResource(); } - public function test_new_models_are_supplied_by_the_adapter() + public function test_resource_creatable_callback_receives_correct_parameters() { - $this->markTestIncomplete(); + $called = false; + + $this->api->resource('users', new MockAdapter(), function (Type $type) use (&$called) { + $type->creatable(function ($request) use (&$called) { + $this->assertInstanceOf(ServerRequestInterface::class, $request); + return $called = true; + }); + }); + + $this->createResource(); + + $this->assertTrue($called); + } + + public function test_new_models_are_supplied_and_saved_by_the_adapter() + { + $adapter = $this->prophesize(AdapterInterface::class); + $adapter->create()->willReturn($createdModel = (object) []); + $adapter->save($createdModel)->shouldBeCalled(); + $adapter->getId($createdModel)->willReturn('1'); + + $this->api->resource('users', $adapter->reveal(), function (Type $type) { + $type->creatable(); + }); + + $this->createResource(); } public function test_resources_can_provide_custom_models() { - $this->markTestIncomplete(); + $createdModel = (object) []; + + $adapter = $this->prophesize(AdapterInterface::class); + $adapter->create()->shouldNotBeCalled(); + $adapter->save($createdModel)->shouldBeCalled(); + $adapter->getId($createdModel)->willReturn('1'); + + $this->api->resource('users', $adapter->reveal(), function (Type $type) use ($createdModel) { + $type->creatable(); + $type->create(function ($request) use ($createdModel) { + $this->assertInstanceOf(ServerRequestInterface::class, $request); + return $createdModel; + }); + }); + + $this->createResource(); } - public function test_creating_a_resource_calls_the_save_adapter_method() + public function test_resources_can_provide_custom_savers() { - $this->markTestIncomplete(); + $called = false; + + $adapter = $this->prophesize(AdapterInterface::class); + $adapter->create()->willReturn($createdModel = (object) []); + $adapter->save($createdModel)->shouldNotBeCalled(); + $adapter->getId($createdModel)->willReturn('1'); + + $this->api->resource('users', $adapter->reveal(), function (Type $type) use ($createdModel, &$called) { + $type->creatable(); + $type->save(function ($model, $request) use ($createdModel, &$called) { + $model->id = '1'; + $this->assertSame($createdModel, $model); + $this->assertInstanceOf(ServerRequestInterface::class, $request); + return $called = true; + }); + }); + + $this->createResource(); + + $this->assertTrue($called); } - // saver... - // listeners... + public function test_resources_can_have_creation_listeners() + { + $called = 0; + + $adapter = $this->prophesize(AdapterInterface::class); + $adapter->create()->willReturn($createdModel = (object) []); + $adapter->getId($createdModel)->willReturn('1'); + + $this->api->resource('users', $adapter->reveal(), function (Type $type) use ($adapter, $createdModel, &$called) { + $type->creatable(); + $type->creating(function ($model, $request) use ($adapter, $createdModel, &$called) { + $this->assertSame($createdModel, $model); + $this->assertInstanceOf(ServerRequestInterface::class, $request); + $adapter->save($createdModel)->shouldNotHaveBeenCalled(); + $called++; + }); + $type->created(function ($model, $request) use ($adapter, $createdModel, &$called) { + $this->assertSame($createdModel, $model); + $this->assertInstanceOf(ServerRequestInterface::class, $request); + $adapter->save($createdModel)->shouldHaveBeenCalled(); + $called++; + }); + }); + + $this->createResource(); + + $this->assertEquals(2, $called); + } } diff --git a/tests/feature/DeleteTest.php b/tests/feature/DeleteTest.php index 9d77b9f..b35341d 100644 --- a/tests/feature/DeleteTest.php +++ b/tests/feature/DeleteTest.php @@ -11,7 +11,11 @@ namespace Tobyz\Tests\JsonApiServer\feature; +use Psr\Http\Message\ServerRequestInterface; +use Tobyz\JsonApiServer\Adapter\AdapterInterface; +use Tobyz\JsonApiServer\Exception\ForbiddenException; use Tobyz\JsonApiServer\JsonApi; +use Tobyz\JsonApiServer\Schema\Type; use Tobyz\Tests\JsonApiServer\AbstractTestCase; use Tobyz\Tests\JsonApiServer\MockAdapter; @@ -22,48 +26,161 @@ class DeleteTest extends AbstractTestCase */ private $api; - /** - * @var MockAdapter - */ - private $adapter; - public function setUp(): void { $this->api = new JsonApi('http://example.com'); + } - $this->adapter = new MockAdapter(); + protected function deleteResource(array $data = []) + { + return $this->api->handle( + $this->buildRequest('DELETE', '/users/1') + ); } public function test_resources_are_not_deletable_by_default() { - $this->markTestIncomplete(); + $this->api->resource('users', new MockAdapter()); + + $this->expectException(ForbiddenException::class); + + $this->deleteResource(); } public function test_resource_deletion_can_be_explicitly_enabled() { - $this->markTestIncomplete(); + $this->api->resource('users', new MockAdapter(), function (Type $type) { + $type->deletable(); + }); + + $response = $this->deleteResource(); + + $this->assertEquals(204, $response->getStatusCode()); } public function test_resource_deletion_can_be_conditionally_enabled() { - $this->markTestIncomplete(); + $this->api->resource('users', new MockAdapter(), function (Type $type) { + $type->deletable(function () { + return true; + }); + }); + + $response = $this->deleteResource(); + + $this->assertEquals(204, $response->getStatusCode()); } public function test_resource_deletion_can_be_explicitly_disabled() { - $this->markTestIncomplete(); + $this->api->resource('users', new MockAdapter(), function (Type $type) { + $type->notDeletable(); + }); + + $this->expectException(ForbiddenException::class); + + $this->deleteResource(); } public function test_resource_deletion_can_be_conditionally_disabled() { - $this->markTestIncomplete(); + $this->api->resource('users', new MockAdapter(), function (Type $type) { + $type->deletable(function () { + return false; + }); + }); + + $this->expectException(ForbiddenException::class); + + $this->deleteResource(); + } + + public function test_resource_deletable_callback_receives_correct_parameters() + { + $called = false; + + $adapter = $this->prophesize(AdapterInterface::class); + $adapter->query()->willReturn($query = (object) []); + $adapter->find($query, '1')->willReturn($deletingModel = (object) []); + $adapter->delete($deletingModel); + + $this->api->resource('users', $adapter->reveal(), function (Type $type) use ($deletingModel, &$called) { + $type->deletable(function ($model, $request) use ($deletingModel, &$called) { + $this->assertSame($deletingModel, $model); + $this->assertInstanceOf(ServerRequestInterface::class, $request); + return $called = true; + }); + }); + + $this->deleteResource(); + + $this->assertTrue($called); } public function test_deleting_a_resource_calls_the_delete_adapter_method() { - $this->markTestIncomplete(); + $adapter = $this->prophesize(AdapterInterface::class); + $adapter->query()->willReturn($query = (object) []); + $adapter->find($query, '1')->willReturn($model = (object) []); + $adapter->delete($model)->shouldBeCalled(); + + $this->api->resource('users', $adapter->reveal(), function (Type $type) { + $type->deletable(); + }); + + $this->deleteResource(); } - // deleter... - // listeners... + public function test_resources_can_provide_custom_deleters() + { + $called = false; + + $adapter = $this->prophesize(AdapterInterface::class); + $adapter->query()->willReturn($query = (object) []); + $adapter->find($query, '1')->willReturn($deletingModel = (object) []); + $adapter->delete($deletingModel)->shouldNotBeCalled(); + + $this->api->resource('users', $adapter->reveal(), function (Type $type) use ($deletingModel, &$called) { + $type->deletable(); + $type->delete(function ($model, $request) use ($deletingModel, &$called) { + $this->assertSame($deletingModel, $model); + $this->assertInstanceOf(ServerRequestInterface::class, $request); + return $called = true; + }); + }); + + $this->deleteResource(); + + $this->assertTrue($called); + } + + public function test_resources_can_have_deletion_listeners() + { + $called = 0; + + $adapter = $this->prophesize(AdapterInterface::class); + $adapter->query()->willReturn($query = (object) []); + $adapter->find($query, '1')->willReturn($deletingModel = (object) []); + $adapter->delete($deletingModel)->shouldBeCalled(); + + $this->api->resource('users', $adapter->reveal(), function (Type $type) use ($adapter, $deletingModel, &$called) { + $type->deletable(); + $type->deleting(function ($model, $request) use ($adapter, $deletingModel, &$called) { + $this->assertSame($deletingModel, $model); + $this->assertInstanceOf(ServerRequestInterface::class, $request); + $adapter->delete($deletingModel)->shouldNotHaveBeenCalled(); + $called++; + }); + $type->deleted(function ($model, $request) use ($adapter, $deletingModel, &$called) { + $this->assertSame($deletingModel, $model); + $this->assertInstanceOf(ServerRequestInterface::class, $request); + $adapter->delete($deletingModel)->shouldHaveBeenCalled(); + $called++; + }); + }); + + $this->deleteResource(); + + $this->assertEquals(2, $called); + } } diff --git a/tests/feature/FieldVisibilityTest.php b/tests/feature/FieldVisibilityTest.php index 0ede2c4..dd81770 100644 --- a/tests/feature/FieldVisibilityTest.php +++ b/tests/feature/FieldVisibilityTest.php @@ -78,7 +78,7 @@ class FieldVisibilityTest extends AbstractTestCase $this->assertArrayHasKey('visibleHasMany', $relationships); } - public function test_attributes_can_be_conditionally_visible() + public function test_fields_can_be_conditionally_visible() { $this->markTestIncomplete(); diff --git a/tests/feature/MetaTest.php b/tests/feature/MetaTest.php index d25474a..699875c 100644 --- a/tests/feature/MetaTest.php +++ b/tests/feature/MetaTest.php @@ -11,7 +11,9 @@ namespace Tobyz\Tests\JsonApiServer\feature; +use Psr\Http\Message\ServerRequestInterface; use Tobyz\JsonApiServer\JsonApi; +use Tobyz\JsonApiServer\Schema\Type; use Tobyz\Tests\JsonApiServer\AbstractTestCase; use Tobyz\Tests\JsonApiServer\MockAdapter; @@ -22,25 +24,44 @@ class MetaTest extends AbstractTestCase */ private $api; - /** - * @var MockAdapter - */ - private $adapter; - public function setUp(): void { $this->api = new JsonApi('http://example.com'); - - $this->adapter = new MockAdapter(); } - public function test_meta_fields_can_be_added_to_the_document_with_a_value() + public function test_meta_fields_can_be_added_to_resources_with_a_value() { - $this->markTestIncomplete(); + $this->api->resource('users', new MockAdapter(), function (Type $type) { + $type->meta('foo', 'bar'); + }); + + $response = $this->api->handle( + $this->buildRequest('GET', '/users/1') + ); + + $document = json_decode($response->getBody(), true); + + $this->assertEquals(['foo' => 'bar'], $document['data']['meta']); } - public function test_meta_fields_can_be_added_to_the_document_with_a_closure() + public function test_meta_fields_can_be_added_to_resources_with_a_closure() { - $this->markTestIncomplete(); + $adapter = new MockAdapter(['1' => (object) ['id' => '1']]); + + $this->api->resource('users', $adapter, function (Type $type) use ($adapter) { + $type->meta('foo', function ($model, $request) use ($adapter) { + $this->assertSame($adapter->models['1'], $model); + $this->assertInstanceOf(ServerRequestInterface::class, $request); + return 'bar'; + }); + }); + + $response = $this->api->handle( + $this->buildRequest('GET', '/users/1') + ); + + $document = json_decode($response->getBody(), true); + + $this->assertEquals('bar', $document['data']['meta']['foo']); } } diff --git a/tests/feature/ScopesTest.php b/tests/feature/ScopesTest.php index 8e2a786..abbce52 100644 --- a/tests/feature/ScopesTest.php +++ b/tests/feature/ScopesTest.php @@ -12,7 +12,6 @@ namespace Tobyz\Tests\JsonApiServer\feature; use Psr\Http\Message\ServerRequestInterface; -use Tobyz\JsonApiServer\Exception\BadRequestException; use Tobyz\JsonApiServer\JsonApi; use Tobyz\JsonApiServer\Schema\Type; use Tobyz\Tests\JsonApiServer\AbstractTestCase;