This commit is contained in:
Toby Zerner 2019-08-19 18:36:16 +02:00
parent 269207eedb
commit ca9cd62711
13 changed files with 136 additions and 890 deletions

View File

@ -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('/(?<!^)[A-Z]/', '_$0', $attribute->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();

View File

@ -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;

View File

@ -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')) {

View File

@ -162,6 +162,9 @@ abstract class Field
return $this->default;
}
/**
* @return bool|Closure
*/
public function getFilterable()
{
return $this->filterable;

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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)
]);
}
}

View File

@ -1,202 +0,0 @@
<?php
/*
* This file is part of JSON-API.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* 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']
);
}
}

View File

@ -1,60 +0,0 @@
<?php
/*
* This file is part of JSON-API.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* 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);
}
}

View File

@ -1,600 +0,0 @@
<?php
/*
* This file is part of JSON-API.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* 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
);
}
}

View File

@ -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();
}

View File

@ -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()

View File

@ -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);
}
}