diff --git a/src/Adapter/EloquentAdapter.php b/src/Adapter/EloquentAdapter.php index 9d4cb33..122bf95 100644 --- a/src/Adapter/EloquentAdapter.php +++ b/src/Adapter/EloquentAdapter.php @@ -138,15 +138,15 @@ class EloquentAdapter implements AdapterInterface public function filterByAttribute($query, Attribute $attribute, $value): void { - $property = $this->getAttributeProperty($attribute); + $column = $this->getAttributeColumn($attribute); // TODO: extract this into non-adapter territory if (preg_match('/(.+)\.\.(.+)/', $value, $matches)) { if ($matches[1] !== '*') { - $query->where($property, '>=', $matches[1]); + $query->where($column, '>=', $matches[1]); } if ($matches[2] !== '*') { - $query->where($property, '<=', $matches[2]); + $query->where($column, '<=', $matches[2]); } return; @@ -154,13 +154,13 @@ class EloquentAdapter implements AdapterInterface foreach (['>=', '>', '<=', '<'] as $operator) { if (strpos($value, $operator) === 0) { - $query->where($property, $operator, substr($value, strlen($operator))); + $query->where($column, $operator, substr($value, strlen($operator))); return; } } - $query->where($property, $value); + $query->where($column, $value); } public function filterByHasOne($query, HasOne $relationship, array $ids): void @@ -181,9 +181,9 @@ class EloquentAdapter implements AdapterInterface }); } - public function sortByAttribute($query, Attribute $field, string $direction): void + public function sortByAttribute($query, Attribute $attribute, string $direction): void { - $query->orderBy($this->getAttributeProperty($field), $direction); + $query->orderBy($this->getAttributeColumn($attribute), $direction); } public function paginate($query, int $limit, int $offset): void @@ -224,6 +224,11 @@ class EloquentAdapter implements AdapterInterface return $attribute->getProperty() ?: strtolower(preg_replace('/(?getName())); } + private function getAttributeColumn(Attribute $attribute): string + { + return $this->model->getTable().'.'.$this->getAttributeProperty($attribute); + } + private function getRelationshipProperty(Relationship $relationship): string { return $relationship->getProperty() ?: $relationship->getName(); diff --git a/src/Handler/Concerns/IncludesData.php b/src/Handler/Concerns/IncludesData.php index a34a734..da59b5f 100644 --- a/src/Handler/Concerns/IncludesData.php +++ b/src/Handler/Concerns/IncludesData.php @@ -103,6 +103,7 @@ trait IncludesData $adapter = $this->resource->getAdapter(); $fields = $this->resource->getSchema()->getFields(); + // TODO: don't load IDs for relationships which are included below foreach ($fields as $name => $field) { if (! $field instanceof Relationship || ! evaluate($field->getLinkage(), [$request]) || ! $field->getLoadable()) { continue; diff --git a/src/Handler/Index.php b/src/Handler/Index.php index 71a5723..923bdcf 100644 --- a/src/Handler/Index.php +++ b/src/Handler/Index.php @@ -13,13 +13,11 @@ use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Server\RequestHandlerInterface; use Tobyz\JsonApiServer\JsonApi; use Tobyz\JsonApiServer\Exception\BadRequestException; -use Tobyz\JsonApiServer\Exception\ForbiddenException; use Tobyz\JsonApiServer\JsonApiResponse; use Tobyz\JsonApiServer\ResourceType; use Tobyz\JsonApiServer\Schema\Attribute; use Tobyz\JsonApiServer\Schema\HasMany; use Tobyz\JsonApiServer\Schema\HasOne; -use Tobyz\JsonApiServer\Schema\Type; use Tobyz\JsonApiServer\Serializer; class Index implements RequestHandlerInterface @@ -47,7 +45,7 @@ class Index implements RequestHandlerInterface $query = $adapter->query(); foreach ($schema->getScopes() as $scope) { - $request = $scope($request, $query) ?: $request; + $request = $scope($query, $request, null) ?: $request; } if ($filter = $request->getAttribute('jsonApiFilter')) { diff --git a/src/Schema/Field.php b/src/Schema/Field.php index 8dc2dda..08ee5b3 100644 --- a/src/Schema/Field.php +++ b/src/Schema/Field.php @@ -162,6 +162,9 @@ abstract class Field return $this->default; } + /** + * @return bool|Closure + */ public function getFilterable() { return $this->filterable; diff --git a/src/Schema/Relationship.php b/src/Schema/Relationship.php index 5bf8d01..f1a0c8f 100644 --- a/src/Schema/Relationship.php +++ b/src/Schema/Relationship.php @@ -48,12 +48,12 @@ abstract class Relationship extends Field return $this; } - // public function notLoadable() - // { - // $this->loadable = false; - // - // return $this; - // } + public function notLoadable() + { + $this->loadable = false; + + return $this; + } public function includable() { @@ -98,6 +98,9 @@ abstract class Relationship extends Field return $this->links; } + /** + * @return bool|Closure + */ public function getLoadable() { return $this->loadable; diff --git a/src/Schema/Type.php b/src/Schema/Type.php index e12eb0f..43390b6 100644 --- a/src/Schema/Type.php +++ b/src/Schema/Type.php @@ -16,6 +16,7 @@ final class Type private $limit = 50; private $countable = true; private $defaultSort; + private $defaultFilter; private $scopes = []; private $saver; private $creatable = false; @@ -80,6 +81,15 @@ final class Type return $this->meta; } + public function filter(string $name, Closure $callback) + { + $this->attribute($name) + ->hidden() + ->filterable($callback); + + return $this; + } + public function paginate(int $perPage) { $this->paginate = $perPage; @@ -90,7 +100,7 @@ final class Type $this->paginate = null; } - public function getPaginate(): int + public function getPaginate(): ?int { return $this->paginate; } @@ -279,4 +289,16 @@ final class Type { return $this->defaultSort; } + + public function defaultFilter(?array $filter) + { + $this->defaultFilter = $filter; + + return $this; + } + + public function getDefaultFilter() + { + return $this->defaultFilter; + } } diff --git a/src/Serializer.php b/src/Serializer.php index 5d8325f..f8fb1b5 100644 --- a/src/Serializer.php +++ b/src/Serializer.php @@ -98,7 +98,7 @@ final class Serializer ksort($metas); foreach ($metas as $name => $meta) { - $data['meta'][$name] = new Structure\Meta($meta->name, ($meta->value)($this->request, $model)); + $data['meta'][$name] = new Structure\Meta($meta->getName(), ($meta->getValue())($model, $this->request)); } $this->merge($data); @@ -270,11 +270,11 @@ final class Serializer private function relatedResourceIdentifier(Schema\Relationship $field, $model) { - $relatedResource = $this->api->getResource($field->resource); + $relatedResource = $this->api->getResource($type = $field->getType()); return $this->resourceIdentifier([ - 'type' => $field->resource, - 'id' => $relatedResource->getAdapter()->getId($model) + 'type' => $type, + 'id' => is_string($model) ? $model : $relatedResource->getAdapter()->getId($model) ]); } } diff --git a/tests/CreateTest.old.php b/tests/CreateTest.old.php deleted file mode 100644 index 007bbeb..0000000 --- a/tests/CreateTest.old.php +++ /dev/null @@ -1,202 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Tobyz\Tests\JsonApiServer; - -use Tobyz\JsonApiServer\JsonApi; -use Tobyz\JsonApiServer\Exception\BadRequestException; -use Tobyz\JsonApiServer\Exception\ForbiddenException; -use Tobyz\JsonApiServer\Serializer; -use Tobyz\JsonApiServer\Schema\Type; -use Psr\Http\Message\ServerRequestInterface as Request; -use JsonApiPhp\JsonApi; -use Zend\Diactoros\ServerRequest; -use Zend\Diactoros\Uri; - -class CreateTest extends AbstractTestCase -{ - public function testResourceNotCreatableByDefault() - { - $api = new JsonApi('http://example.com'); - - $api->resource('users', new MockAdapter(), function (Type $schema) { - // - }); - - $request = $this->buildRequest('POST', '/users'); - - $this->expectException(ForbiddenException::class); - $this->expectExceptionMessage('You cannot create this resource'); - - $api->handle($request); - } - - public function testCreateResourceValidatesBody() - { - $api = new JsonApi('http://example.com'); - - $api->resource('users', new MockAdapter(), function (Type $schema) { - $schema->creatable(); - }); - - $request = $this->buildRequest('POST', '/users'); - - $this->expectException(BadRequestException::class); - - $api->handle($request); - } - - public function testCreateResource() - { - $api = new JsonApi('http://example.com'); - - $api->resource('users', $adapter = new MockAdapter(), function (Type $schema) { - $schema->creatable(); - - $schema->attribute('name')->writable(); - }); - - $request = $this->buildRequest('POST', '/users') - ->withParsedBody([ - 'data' => [ - 'type' => 'users', - 'id' => '1', - 'attributes' => [ - 'name' => 'Toby' - ] - ] - ]); - - $response = $api->handle($request); - - $this->assertTrue($adapter->createdModel->saveWasCalled); - - $this->assertEquals(201, $response->getStatusCode()); - $this->assertEquals( - [ - 'type' => 'users', - 'id' => '1', - 'attributes' => [ - 'name' => 'Toby' - ], - 'links' => [ - 'self' => 'http://example.com/users/1' - ] - ], - json_decode($response->getBody(), true)['data'] - ); - } - - public function testAttributeWritable() - { - $request = $this->buildRequest('POST', '/users') - ->withParsedBody([ - 'data' => [ - 'type' => 'users', - 'id' => '1', - 'attributes' => [ - 'writable1' => 'value', - 'writable2' => 'value', - 'writable3' => 'value', - ] - ] - ]); - - $api = new JsonApi('http://example.com'); - - $api->resource('users', $adapter = new MockAdapter(), function (Type $schema) use ($adapter, $request) { - $schema->creatable(); - - $schema->attribute('writable1')->writable(); - - $schema->attribute('writable2')->writableIf(function ($arg1, $arg2) use ($adapter, $request) { - $this->assertInstanceOf(Request::class, $arg1); - $this->assertEquals($adapter->createdModel, $arg2); - return true; - }); - - $schema->attribute('writable3')->readonlyIf(function ($arg1, $arg2) use ($adapter, $request) { - $this->assertInstanceOf(Request::class, $arg1); - $this->assertEquals($adapter->createdModel, $arg2); - return false; - }); - }); - - $response = $api->handle($request); - - $this->assertEquals(201, $response->getStatusCode()); - } - - public function testAttributeReadonly() - { - $request = $this->buildRequest('POST', '/users') - ->withParsedBody([ - 'data' => [ - 'type' => 'users', - 'id' => '1', - 'attributes' => [ - 'readonly' => 'value', - ] - ] - ]); - - $api = new JsonApi('http://example.com'); - - $api->resource('users', $adapter = new MockAdapter(), function (Type $schema) use ($adapter, $request) { - $schema->creatable(); - - $schema->attribute('readonly')->readonly(); - }); - - $this->expectException(BadRequestException::class); - $this->expectExceptionMessage('Field [readonly] is not writable'); - - $api->handle($request); - } - - public function testAttributeDefault() - { - $request = $this->buildRequest('POST', '/users') - ->withParsedBody([ - 'data' => [ - 'type' => 'users', - 'id' => '1', - 'attributes' => [ - 'attribute3' => 'userValue' - ] - ] - ]); - - $api = new JsonApi('http://example.com'); - - $api->resource('users', $adapter = new MockAdapter(), function (Type $schema) use ($request) { - $schema->creatable(); - - $schema->attribute('attribute1')->default('defaultValue'); - $schema->attribute('attribute2')->default(function ($arg1) use ($request) { - $this->assertInstanceOf(Request::class, $arg1); - return 'defaultValue'; - }); - $schema->attribute('attribute3')->writable()->default('defaultValue'); - }); - - $response = $api->handle($request); - - $this->assertEquals( - [ - 'attribute1' => 'defaultValue', - 'attribute2' => 'defaultValue', - 'attribute3' => 'userValue' - ], - json_decode($response->getBody(), true)['data']['attributes'] - ); - } -} diff --git a/tests/DeleteTest.old.php b/tests/DeleteTest.old.php deleted file mode 100644 index 3f1574d..0000000 --- a/tests/DeleteTest.old.php +++ /dev/null @@ -1,60 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Tobyz\Tests\JsonApiServer; - -use Tobyz\JsonApiServer\JsonApi; -use Tobyz\JsonApiServer\Exception\BadRequestException; -use Tobyz\JsonApiServer\Exception\ForbiddenException; -use Tobyz\JsonApiServer\Serializer; -use Tobyz\JsonApiServer\Schema\Type; -use Psr\Http\Message\ServerRequestInterface as Request; -use JsonApiPhp\JsonApi; -use Zend\Diactoros\ServerRequest; -use Zend\Diactoros\Uri; - -class DeleteTest extends AbstractTestCase -{ - public function testResourceNotDeletableByDefault() - { - $api = new JsonApi('http://example.com'); - - $api->resource('users', new MockAdapter(), function (Type $schema) { - // - }); - - $request = $this->buildRequest('DELETE', '/users/1'); - - $this->expectException(ForbiddenException::class); - $this->expectExceptionMessage('You cannot delete this resource'); - - $api->handle($request); - } - - public function testDeleteResource() - { - $usersAdapter = new MockAdapter([ - '1' => $user = (object)['id' => '1'] - ]); - - $api = new JsonApi('http://example.com'); - - $api->resource('users', $usersAdapter, function (Type $schema) { - $schema->deletable(); - }); - - $request = $this->buildRequest('DELETE', '/users/1'); - $response = $api->handle($request); - - $this->assertEquals(204, $response->getStatusCode()); - $this->assertTrue($user->deleteWasCalled); - } -} diff --git a/tests/ShowTest.old.php b/tests/ShowTest.old.php deleted file mode 100644 index 46a4161..0000000 --- a/tests/ShowTest.old.php +++ /dev/null @@ -1,600 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Tobyz\Tests\JsonApiServer; - -use Tobyz\JsonApiServer\JsonApi; -use Tobyz\JsonApiServer\Exception\BadRequestException; -use Tobyz\JsonApiServer\Schema\Type; -use Psr\Http\Message\ServerRequestInterface as Request; - -class ShowTest extends AbstractTestCase -{ - public function test_resource_with_no_fields() - { - $api = new JsonApi('http://example.com'); - $api->resource('users', new MockAdapter(), function (Type $schema) { - // no fields - }); - - $request = $this->buildRequest('GET', '/users/1'); - $response = $api->handle($request); - - $this->assertEquals($response->getStatusCode(), 200); - $this->assertEquals( - [ - 'data' => [ - 'type' => 'users', - 'id' => '1', - 'links' => [ - 'self' => 'http://example.com/users/1' - ] - ] - ], - json_decode($response->getBody(), true) - ); - } - - public function test_attributes() - { - $adapter = new MockAdapter([ - '1' => (object) [ - 'id' => '1', - 'attribute1' => 'value1', - 'property2' => 'value2', - 'property3' => 'value3' - ] - ]); - - $request = $this->buildRequest('GET', '/users/1'); - - $api = new JsonApi('http://example.com'); - - $api->resource('users', $adapter, function (Type $schema) { - $schema->attribute('attribute1'); - $schema->attribute('attribute2', 'property2'); - $schema->attribute('attribute3')->property('property3'); - }); - - $response = $api->handle($request); - - $this->assertArraySubset( - [ - 'attributes' => [ - 'attribute1' => 'value1', - 'attribute2' => 'value2', - 'attribute3' => 'value3' - ] - ], - json_decode($response->getBody(), true)['data'] - ); - } - - public function testAttributeGetter() - { - $adapter = new MockAdapter([ - '1' => $model = (object) ['id' => '1'] - ]); - - $request = $this->buildRequest('GET', '/users/1'); - - $api = new JsonApi('http://example.com'); - - $api->resource('users', $adapter, function (Type $schema) use ($model, $request) { - $schema->attribute('attribute1') - ->get(function ($arg1, $arg2) use ($model, $request) { - $this->assertInstanceOf(Request::class, $arg1); - $this->assertEquals($model, $arg2); - return 'value1'; - }); - }); - - $response = $api->handle($request); - - $this->assertEquals($response->getStatusCode(), 200); - $this->assertArraySubset( - [ - 'attributes' => [ - 'attribute1' => 'value1' - ] - ], - json_decode($response->getBody(), true)['data'] - ); - } - - public function testAttributeVisibility() - { - $adapter = new MockAdapter([ - '1' => $model = (object) ['id' => '1'] - ]); - - $request = $this->buildRequest('GET', '/users/1'); - - $api = new JsonApi('http://example.com'); - $api->resource('users', $adapter, function (Type $schema) use ($model, $request) { - $schema->attribute('visible1'); - - $schema->attribute('visible2')->visible(); - - $schema->attribute('visible3')->visibleIf(function ($arg1, $arg2) use ($model, $request) { - $this->assertInstanceOf(Request::class, $arg1); - $this->assertEquals($model, $arg2); - return true; - }); - - $schema->attribute('visible4')->hiddenIf(function ($arg1, $arg2) use ($model, $request) { - $this->assertInstanceOf(Request::class, $arg1); - $this->assertEquals($model, $arg2); - return false; - }); - - $schema->attribute('hidden1')->hidden(); - - $schema->attribute('hidden2')->visibleIf(function () { - return false; - }); - - $schema->attribute('hidden3')->hiddenIf(function () { - return true; - }); - }); - - $response = $api->handle($request); - - $attributes = json_decode($response->getBody(), true)['data']['attributes']; - - $this->assertArrayHasKey('visible1', $attributes); - $this->assertArrayHasKey('visible2', $attributes); - $this->assertArrayHasKey('visible3', $attributes); - $this->assertArrayHasKey('visible4', $attributes); - - $this->assertArrayNotHasKey('hidden1', $attributes); - $this->assertArrayNotHasKey('hidden2', $attributes); - $this->assertArrayNotHasKey('hidden3', $attributes); - } - - public function testHasOneRelationship() - { - $phonesAdapter = new MockAdapter([ - '1' => $phone1 = (object) ['id' => '1', 'number' => '8881'], - '2' => $phone2 = (object) ['id' => '2', 'number' => '8882'], - '3' => $phone3 = (object) ['id' => '3', 'number' => '8883'] - ]); - - $usersAdapter = new MockAdapter([ - '1' => (object) [ - 'id' => '1', - 'phone' => $phone1, - 'property2' => $phone2, - 'property3' => $phone3, - ] - ]); - - $request = $this->buildRequest('GET', '/users/1') - ->withQueryParams(['include' => 'phone,phone2,phone3']); - - $api = new JsonApi('http://example.com'); - - $api->resource('users', $usersAdapter, function (Type $schema) { - $schema->hasOne('phone'); - - $schema->hasOne('phone2', 'phones', 'property2'); - - $schema->hasOne('phone3') - ->type('phones') - ->property('property3'); - }); - - $api->resource('phones', $phonesAdapter, function (Type $schema) { - $schema->attribute('number'); - }); - - $response = $api->handle($request); - - $this->assertArraySubset( - [ - 'relationships' => [ - 'phone' => [ - 'data' => ['type' => 'phones', 'id' => '1'] - ], - 'phone2' => [ - 'data' => ['type' => 'phones', 'id' => '2'] - ], - 'phone3' => [ - 'data' => ['type' => 'phones', 'id' => '3'] - ] - ] - ], - json_decode($response->getBody(), true)['data'] - ); - - $this->assertEquals( - [ - [ - 'type' => 'phones', - 'id' => '1', - 'attributes' => ['number' => '8881'], - 'links' => [ - 'self' => 'http://example.com/phones/1' - ] - ], - [ - 'type' => 'phones', - 'id' => '2', - 'attributes' => ['number' => '8882'], - 'links' => [ - 'self' => 'http://example.com/phones/2' - ] - ], - [ - 'type' => 'phones', - 'id' => '3', - 'attributes' => ['number' => '8883'], - 'links' => [ - 'self' => 'http://example.com/phones/3' - ] - ] - ], - json_decode($response->getBody(), true)['included'] - ); - } - - public function testHasOneRelationshipInclusion() - { - $phonesAdapter = new MockAdapter([ - '1' => $phone1 = (object) ['id' => '1', 'number' => '8881'], - '2' => $phone2 = (object) ['id' => '2', 'number' => '8882'] - ]); - - $usersAdapter = new MockAdapter([ - '1' => (object) [ - 'id' => '1', - 'phone' => $phone1, - 'property2' => $phone2 - ] - ]); - - $request = $this->buildRequest('GET', '/users/1') - ->withQueryParams(['include' => 'phone2']); - - $api = new JsonApi('http://example.com'); - - $api->resource('users', $usersAdapter, function (Type $schema) { - $schema->hasOne('phone'); - - $schema->hasOne('phone2', 'phones', 'property2'); - }); - - $api->resource('phones', $phonesAdapter, function (Type $schema) { - $schema->attribute('number'); - }); - - $response = $api->handle($request); - - $this->assertArraySubset( - [ - 'relationships' => [ - 'phone2' => [ - 'data' => ['type' => 'phones', 'id' => '2'] - ] - ] - ], - json_decode($response->getBody(), true)['data'] - ); - - $this->assertEquals( - [ - [ - 'type' => 'phones', - 'id' => '2', - 'attributes' => ['number' => '8882'], - 'links' => [ - 'self' => 'http://example.com/phones/2' - ] - ] - ], - json_decode($response->getBody(), true)['included'] - ); - - $response = $api->handle( - $request->withQueryParams(['include' => 'phone']) - ); - - $this->assertArraySubset( - [ - 'relationships' => [ - 'phone' => [ - 'data' => ['type' => 'phones', 'id' => '1'] - ] - ] - ], - json_decode($response->getBody(), true)['data'] - ); - - $this->assertEquals( - [ - [ - 'type' => 'phones', - 'id' => '1', - 'attributes' => ['number' => '8881'], - 'links' => [ - 'self' => 'http://example.com/phones/1' - ] - ] - ], - json_decode($response->getBody(), true)['included'] - ); - } - - public function testHasManyRelationshipNotIncludableByDefault() - { - $api = new JsonApi('http://example.com'); - - $api->resource('users', new MockAdapter(), function (Type $schema) { - $schema->hasMany('groups'); - }); - - $request = $this->buildRequest('GET', '/users/1') - ->withQueryParams(['include' => 'groups']); - - $this->expectException(BadRequestException::class); - $this->expectExceptionMessage('Invalid include [groups]'); - - $api->handle($request); - } - - public function testHasManyRelationshipNotIncludedByDefault() - { - $usersAdapter = new MockAdapter([ - '1' => (object) [ - 'id' => '1', - 'groups' => [ - (object) ['id' => '1'], - (object) ['id' => '2'], - ] - ] - ]); - - $api = new JsonApi('http://example.com'); - - $api->resource('users', $usersAdapter, function (Type $schema) { - $schema->hasMany('groups'); - }); - - $api->resource('groups', new MockAdapter()); - - $request = $this->buildRequest('GET', '/users/1'); - - $response = $api->handle($request); - - $body = json_decode($response->getBody(), true); - - $this->assertArrayNotHasKey('data', $body['data']['relationships']); - $this->assertArrayNotHasKey('included', $body); - } - - public function testHasManyRelationshipInclusion() - { - $groupsAdapter = new MockAdapter([ - '1' => $group1 = (object) ['id' => '1', 'name' => 'Admin'], - '2' => $group2 = (object) ['id' => '2', 'name' => 'Mod'], - '3' => $group3 = (object) ['id' => '3', 'name' => 'Member'], - '4' => $group4 = (object) ['id' => '4', 'name' => 'Guest'] - ]); - - $usersAdapter = new MockAdapter([ - '1' => $user = (object) [ - 'id' => '1', - 'property1' => [$group1, $group2], - 'property2' => [$group3, $group4], - ] - ]); - - $api = new JsonApi('http://example.com'); - - $relationships = []; - - $api->resource('users', $usersAdapter, function (Type $schema) use (&$relationships) { - $relationships[] = $schema->hasMany('groups1', 'groups', 'property1') - ->includable(); - - $relationships[] = $schema->hasMany('groups2', 'groups', 'property2') - ->includable(); - }); - - $api->resource('groups', $groupsAdapter, function (Type $schema) { - $schema->attribute('name'); - }); - - $request = $this->buildRequest('GET', '/users/1') - ->withQueryParams(['include' => 'groups1']); - - $response = $api->handle($request); - - $this->assertEquals([[$relationships[0]]], $user->load); - - $this->assertArraySubset( - [ - 'relationships' => [ - 'groups1' => [ - 'data' => [ - ['type' => 'groups', 'id' => '1'], - ['type' => 'groups', 'id' => '2'] - ] - ] - ] - ], - json_decode($response->getBody(), true)['data'] - ); - - $this->assertEquals( - [ - [ - 'type' => 'groups', - 'id' => '1', - 'attributes' => ['name' => 'Admin'], - 'links' => [ - 'self' => 'http://example.com/groups/1' - ] - ], - [ - 'type' => 'groups', - 'id' => '2', - 'attributes' => ['name' => 'Mod'], - 'links' => [ - 'self' => 'http://example.com/groups/2' - ] - ] - ], - json_decode($response->getBody(), true)['included'] - ); - - $user->load = []; - - $response = $api->handle( - $request->withQueryParams(['include' => 'groups2']) - ); - - $this->assertEquals([[$relationships[1]]], $user->load); - - $this->assertArraySubset( - [ - 'relationships' => [ - 'groups2' => [ - 'data' => [ - ['type' => 'groups', 'id' => '3'], - ['type' => 'groups', 'id' => '4'], - ] - ] - ] - ], - json_decode($response->getBody(), true)['data'] - ); - - $this->assertEquals( - [ - [ - 'type' => 'groups', - 'id' => '3', - 'attributes' => ['name' => 'Member'], - 'links' => [ - 'self' => 'http://example.com/groups/3' - ] - ], - [ - 'type' => 'groups', - 'id' => '4', - 'attributes' => ['name' => 'Guest'], - 'links' => [ - 'self' => 'http://example.com/groups/4' - ] - ], - ], - json_decode($response->getBody(), true)['included'] - ); - } - - public function testNestedRelationshipInclusion() - { - $groupsAdapter = new MockAdapter([ - '1' => $group1 = (object) ['id' => '1', 'name' => 'Admin'], - '2' => $group2 = (object) ['id' => '2', 'name' => 'Mod'] - ]); - - $usersAdapter = new MockAdapter([ - '1' => $user = (object) ['id' => '1', 'groups' => [$group1, $group2]] - ]); - - $postsAdapter = new MockAdapter([ - '1' => $post = (object) ['id' => '1', 'user' => $user] - ]); - - $api = new JsonApi('http://example.com'); - - $relationships = []; - - $api->resource('posts', $postsAdapter, function (Type $schema) use (&$relationships) { - $relationships[] = $schema->hasOne('user'); - }); - - $api->resource('users', $usersAdapter, function (Type $schema) use (&$relationships) { - $relationships[] = $schema->hasMany('groups')->includable(); - }); - - $api->resource('groups', $groupsAdapter, function (Type $schema) { - $schema->attribute('name'); - }); - - $request = $this->buildRequest('GET', '/posts/1') - ->withQueryParams(['include' => 'user,user.groups']); - - $response = $api->handle($request); - - $this->assertEquals([$relationships[0]], $post->load[0]); - $this->assertEquals($relationships, $post->load[1]); - - $this->assertArraySubset( - [ - 'relationships' => [ - 'user' => [ - 'data' => ['type' => 'users', 'id' => '1'] - ] - ] - ], - json_decode($response->getBody(), true)['data'] - ); - - $included = json_decode($response->getBody(), true)['included']; - - // $this->assertContains( - // [ - // 'type' => 'users', - // 'id' => '1', - // 'relationships' => [ - // 'groups' => [ - // 'data' => [ - // ['type' => 'groups', 'id' => '1'], - // ['type' => 'groups', 'id' => '2'] - // ] - // ] - // ], - // 'links' => [ - // 'self' => 'http://example.com/users/1' - // ] - // ], - // $included - // ); - - $this->assertContains( - [ - 'type' => 'groups', - 'id' => '1', - 'attributes' => ['name' => 'Admin'], - 'links' => [ - 'self' => 'http://example.com/groups/1' - ] - ], - $included - ); - - $this->assertContains( - [ - 'type' => 'groups', - 'id' => '2', - 'attributes' => ['name' => 'Mod'], - 'links' => [ - 'self' => 'http://example.com/groups/2' - ] - ], - $included - ); - } -} diff --git a/tests/feature/FieldFiltersTest.php b/tests/feature/FieldFiltersTest.php index 059036c..2b463e1 100644 --- a/tests/feature/FieldFiltersTest.php +++ b/tests/feature/FieldFiltersTest.php @@ -11,13 +11,28 @@ 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; class FieldFiltersTest extends AbstractTestCase { + /** + * @var JsonApi + */ + private $api; + + /** + * @var MockAdapter + */ + private $adapter; + public function setUp(): void { - + $this->api = new JsonApi('http://example.com'); + $this->adapter = new MockAdapter(); } public function test_resources_can_be_filtered_by_id() @@ -53,6 +68,28 @@ class FieldFiltersTest extends AbstractTestCase // to_one, to_many... public function test_types_can_have_custom_filters() + { + $called = false; + + $this->api->resource('users', $this->adapter, function (Type $type) use (&$called) { + $type->filter('name', function (...$args) use (&$called) { + $this->assertSame($this->adapter->query, $args[0]); + $this->assertEquals('value', $args[1]); + $this->assertInstanceOf(ServerRequestInterface::class, $args[2]); + + $called = true; + }); + }); + + $this->api->handle( + $this->buildRequest('GET', '/users') + ->withQueryParams(['filter' => ['name' => 'value']]) + ); + + $this->assertTrue($called); + } + + public function test_types_can_have_a_default_filter() { $this->markTestIncomplete(); } diff --git a/tests/feature/RelationshipInclusionTest.php b/tests/feature/RelationshipInclusionTest.php index 1cf24d3..bb2af27 100644 --- a/tests/feature/RelationshipInclusionTest.php +++ b/tests/feature/RelationshipInclusionTest.php @@ -45,6 +45,11 @@ class RelationshipInclusionTest extends AbstractTestCase $this->markTestIncomplete(); } + public function test_to_one_relationships_can_be_not_loadable() + { + $this->markTestIncomplete(); + } + // to_many... public function test_multiple_relationships_can_be_included() diff --git a/tests/feature/ScopesTest.php b/tests/feature/ScopesTest.php index e6f71a4..8e2a786 100644 --- a/tests/feature/ScopesTest.php +++ b/tests/feature/ScopesTest.php @@ -30,30 +30,64 @@ class ScopesTest extends AbstractTestCase */ private $adapter; + private $scopeWasCalled = false; + public function setUp(): void { - $this->api = new JsonApi('http://example.com'); - $this->adapter = new MockAdapter(); + $this->scopeWasCalled = false; + + $this->api = new JsonApi('http://example.com'); + $this->api->resource('users', $this->adapter, function (Type $type) { + $type->updatable(); + $type->deletable(); + $type->scope(function (...$args) { + $this->assertSame($this->adapter->query, $args[0]); + $this->assertInstanceOf(ServerRequestInterface::class, $args[1]); + $this->scopeWasCalled = true; + }); + }); } public function test_scopes_are_applied_to_the_resource_listing_query() { - $this->markTestIncomplete(); + $this->api->handle( + $this->buildRequest('GET', '/users') + ); + + $this->assertTrue($this->scopeWasCalled); } public function test_scopes_are_applied_to_the_show_resource_query() { - $this->markTestIncomplete(); + $this->api->handle( + $this->buildRequest('GET', '/users/1') + ); + + $this->assertTrue($this->scopeWasCalled); } public function test_scopes_are_applied_to_the_update_resource_query() { - $this->markTestIncomplete(); + $this->api->handle( + $this->buildRequest('PATCH', '/users/1') + ->withParsedBody([ + 'data' => [ + 'type' => 'users', + 'id' => '1' + ] + ]) + ); + + $this->assertTrue($this->scopeWasCalled); } public function test_scopes_are_applied_to_the_delete_resource_query() { - $this->markTestIncomplete(); + $this->api->handle( + $this->buildRequest('DELETE', '/users/1') + ); + + $this->assertTrue($this->scopeWasCalled); } }