Add jsonapi object, more spec tests
This commit is contained in:
parent
d678a2ed9e
commit
c1dc91c558
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
namespace Tobyz\JsonApiServer\Endpoint\Concerns;
|
namespace Tobyz\JsonApiServer\Endpoint\Concerns;
|
||||||
|
|
||||||
|
use JsonApiPhp\JsonApi\JsonApi;
|
||||||
use JsonApiPhp\JsonApi\Meta;
|
use JsonApiPhp\JsonApi\Meta;
|
||||||
use Tobyz\JsonApiServer\Context;
|
use Tobyz\JsonApiServer\Context;
|
||||||
|
|
||||||
|
|
@ -26,4 +27,9 @@ trait BuildsMeta
|
||||||
|
|
||||||
return $meta;
|
return $meta;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function buildJsonApiObject(Context $context): JsonApi
|
||||||
|
{
|
||||||
|
return new JsonApi('1.1');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,20 +49,22 @@ trait SavesData
|
||||||
throw new BadRequestException('data.type must be present');
|
throw new BadRequestException('data.type must be present');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($body['data']['type'] !== $resourceType->getType()) {
|
if ($model) {
|
||||||
throw new ConflictException('data.type does not match the resource type');
|
if (! isset($body['data']['id'])) {
|
||||||
|
throw new BadRequestException('data.id must be present');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($model) {
|
if ($body['data']['id'] !== $resourceType->getAdapter()->getId($model)) {
|
||||||
$id = $resourceType->getAdapter()->getId($model);
|
|
||||||
|
|
||||||
if (! isset($body['data']['id']) || $body['data']['id'] !== $id) {
|
|
||||||
throw new ConflictException('data.id does not match the resource ID');
|
throw new ConflictException('data.id does not match the resource ID');
|
||||||
}
|
}
|
||||||
} elseif (isset($body['data']['id'])) {
|
} elseif (isset($body['data']['id'])) {
|
||||||
throw new ForbiddenException('Client-generated IDs are not supported');
|
throw new ForbiddenException('Client-generated IDs are not supported');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($body['data']['type'] !== $resourceType->getType()) {
|
||||||
|
throw new ConflictException('data.type does not match the resource type');
|
||||||
|
}
|
||||||
|
|
||||||
if (isset($body['data']['attributes']) && ! is_array($body['data']['attributes'])) {
|
if (isset($body['data']['attributes']) && ! is_array($body['data']['attributes'])) {
|
||||||
throw new BadRequestException('data.attributes must be an object');
|
throw new BadRequestException('data.attributes must be an object');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,8 @@ class Delete
|
||||||
run_callbacks($schema->getListeners('deleted'), [&$model, $context]);
|
run_callbacks($schema->getListeners('deleted'), [&$model, $context]);
|
||||||
|
|
||||||
if (count($meta = $this->buildMeta($context))) {
|
if (count($meta = $this->buildMeta($context))) {
|
||||||
|
$meta[] = $this->buildJsonApiObject($context);
|
||||||
|
|
||||||
return json_api_response(
|
return json_api_response(
|
||||||
new MetaDocument(...$meta)
|
new MetaDocument(...$meta)
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,7 @@ class Index
|
||||||
),
|
),
|
||||||
new Structure\Included(...$included),
|
new Structure\Included(...$included),
|
||||||
new Structure\Link\SelfLink($this->buildUrl($context->getRequest())),
|
new Structure\Link\SelfLink($this->buildUrl($context->getRequest())),
|
||||||
|
$this->buildJsonApiObject($context),
|
||||||
...$meta
|
...$meta
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
@ -133,6 +134,8 @@ class Index
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ksort($queryParams);
|
||||||
|
|
||||||
$queryString = http_build_query($queryParams, '', '&', PHP_QUERY_RFC3986);
|
$queryString = http_build_query($queryParams, '', '&', PHP_QUERY_RFC3986);
|
||||||
|
|
||||||
return $selfUrl.($queryString ? '?'.$queryString : '');
|
return $selfUrl.($queryString ? '?'.$queryString : '');
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ class Show
|
||||||
new CompoundDocument(
|
new CompoundDocument(
|
||||||
$primary[0],
|
$primary[0],
|
||||||
new Included(...$included),
|
new Included(...$included),
|
||||||
|
$this->buildJsonApiObject($context),
|
||||||
...$this->buildMeta($context)
|
...$this->buildMeta($context)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,13 @@ class MockAdapter implements AdapterInterface
|
||||||
|
|
||||||
public function get($query): array
|
public function get($query): array
|
||||||
{
|
{
|
||||||
return array_values($this->models);
|
$results = array_values($this->models);
|
||||||
|
|
||||||
|
if (isset($query->paginate)) {
|
||||||
|
$results = array_slice($results, $query->paginate['offset'], $query->paginate['limit']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $results;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId($model): string
|
public function getId($model): string
|
||||||
|
|
@ -124,7 +130,7 @@ class MockAdapter implements AdapterInterface
|
||||||
|
|
||||||
public function paginate($query, int $limit, int $offset): void
|
public function paginate($query, int $limit, int $offset): void
|
||||||
{
|
{
|
||||||
$query->paginate[] = [$limit, $offset];
|
$query->paginate = compact('limit', 'offset');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function load(array $models, array $relationships, $scope, bool $linkage): void
|
public function load(array $models, array $relationships, $scope, bool $linkage): void
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,13 @@
|
||||||
|
|
||||||
namespace Tobyz\Tests\JsonApiServer\specification;
|
namespace Tobyz\Tests\JsonApiServer\specification;
|
||||||
|
|
||||||
|
use Tobyz\JsonApiServer\Exception\ResourceNotFoundException;
|
||||||
use Tobyz\JsonApiServer\JsonApi;
|
use Tobyz\JsonApiServer\JsonApi;
|
||||||
use Tobyz\Tests\JsonApiServer\AbstractTestCase;
|
use Tobyz\Tests\JsonApiServer\AbstractTestCase;
|
||||||
use Tobyz\Tests\JsonApiServer\MockAdapter;
|
use Tobyz\Tests\JsonApiServer\MockAdapter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see https://jsonapi.org/format/#fetching-resources
|
* @see https://jsonapi.org/format/1.1/#fetching-resources
|
||||||
*/
|
*/
|
||||||
class FetchingResourcesTest extends AbstractTestCase
|
class FetchingResourcesTest extends AbstractTestCase
|
||||||
{
|
{
|
||||||
|
|
@ -25,50 +26,80 @@ class FetchingResourcesTest extends AbstractTestCase
|
||||||
*/
|
*/
|
||||||
private $api;
|
private $api;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var MockAdapter
|
|
||||||
*/
|
|
||||||
private $adapter;
|
|
||||||
|
|
||||||
public function setUp(): void
|
public function setUp(): void
|
||||||
{
|
{
|
||||||
$this->api = new JsonApi('http://example.com');
|
$this->api = new JsonApi('http://example.com');
|
||||||
|
|
||||||
$this->adapter = new MockAdapter();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_data_for_resource_collection_is_array_of_resource_objects()
|
public function test_data_for_resource_collection_is_array_of_resource_objects()
|
||||||
{
|
{
|
||||||
$this->markTestIncomplete();
|
$adapter = new MockAdapter([
|
||||||
|
(object) ['id' => '1'],
|
||||||
|
(object) ['id' => '2'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->api->resourceType('articles', $adapter);
|
||||||
|
|
||||||
|
$response = $this->api->handle(
|
||||||
|
$this->buildRequest('GET', '/articles')
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertJsonApiDocumentSubset([
|
||||||
|
'data' => [
|
||||||
|
['type' => 'articles', 'id' => '1'],
|
||||||
|
['type' => 'articles', 'id' => '2'],
|
||||||
|
]
|
||||||
|
], $response->getBody());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_data_for_empty_resource_collection_is_empty_array()
|
public function test_data_for_empty_resource_collection_is_empty_array()
|
||||||
{
|
{
|
||||||
$this->markTestIncomplete();
|
$this->api->resourceType('articles', new MockAdapter());
|
||||||
|
|
||||||
|
$response = $this->api->handle(
|
||||||
|
$this->buildRequest('GET', '/articles')
|
||||||
|
);
|
||||||
|
|
||||||
|
$data = json_decode($response->getBody(), true)['data'] ?? null;
|
||||||
|
|
||||||
|
$this->assertIsArray($data);
|
||||||
|
$this->assertEmpty($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_data_for_individual_resource_is_resource_object()
|
public function test_data_for_individual_resource_is_resource_object()
|
||||||
{
|
{
|
||||||
$this->markTestIncomplete();
|
$adapter = new MockAdapter([
|
||||||
|
(object) ['id' => '1'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->api->resourceType('articles', $adapter);
|
||||||
|
|
||||||
|
$response = $this->api->handle(
|
||||||
|
$this->buildRequest('GET', '/articles/1')
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertJsonApiDocumentSubset([
|
||||||
|
'data' => ['type' => 'articles', 'id' => '1'],
|
||||||
|
], $response->getBody());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_not_found_error_if_resource_type_does_not_exist()
|
public function test_not_found_error_if_resource_type_does_not_exist()
|
||||||
{
|
{
|
||||||
$this->markTestIncomplete();
|
$this->expectException(ResourceNotFoundException::class);
|
||||||
|
|
||||||
|
$this->api->handle(
|
||||||
|
$this->buildRequest('GET', '/articles/1')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_not_found_error_if_resource_does_not_exist()
|
public function test_not_found_error_if_resource_does_not_exist()
|
||||||
{
|
{
|
||||||
$this->markTestIncomplete();
|
$this->expectException(ResourceNotFoundException::class);
|
||||||
}
|
|
||||||
|
|
||||||
public function test_resource_collection_document_contains_self_link()
|
$this->api->resourceType('articles', new MockAdapter());
|
||||||
{
|
|
||||||
$this->markTestIncomplete();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function test_resource_document_contains_self_link()
|
$this->api->handle(
|
||||||
{
|
$this->buildRequest('GET', '/articles/404')
|
||||||
$this->markTestIncomplete();
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,29 +16,32 @@ use Tobyz\Tests\JsonApiServer\AbstractTestCase;
|
||||||
use Tobyz\Tests\JsonApiServer\MockAdapter;
|
use Tobyz\Tests\JsonApiServer\MockAdapter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see https://jsonapi.org/format/#document-jsonapi-object
|
* @see https://jsonapi.org/format/1.1/#document-jsonapi-object
|
||||||
*/
|
*/
|
||||||
class JsonApiTest extends AbstractTestCase
|
class JsonApiObjectTest extends AbstractTestCase
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var JsonApi
|
* @var JsonApi
|
||||||
*/
|
*/
|
||||||
private $api;
|
private $api;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var MockAdapter
|
|
||||||
*/
|
|
||||||
private $adapter;
|
|
||||||
|
|
||||||
public function setUp(): void
|
public function setUp(): void
|
||||||
{
|
{
|
||||||
$this->api = new JsonApi('http://example.com');
|
$this->api = new JsonApi('http://example.com');
|
||||||
|
|
||||||
$this->adapter = new MockAdapter();
|
$this->api->resourceType('articles', new MockAdapter());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_document_includes_jsonapi_member_with_version_1_0()
|
public function test_document_includes_jsonapi_member_with_version_1_1()
|
||||||
{
|
{
|
||||||
$this->markTestIncomplete();
|
$response = $this->api->handle(
|
||||||
|
$this->buildRequest('GET', '/articles')
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertJsonApiDocumentSubset([
|
||||||
|
'jsonapi' => [
|
||||||
|
'version' => '1.1',
|
||||||
|
],
|
||||||
|
], $response->getBody());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -12,12 +12,12 @@
|
||||||
namespace Tobyz\Tests\JsonApiServer\specification;
|
namespace Tobyz\Tests\JsonApiServer\specification;
|
||||||
|
|
||||||
use Tobyz\JsonApiServer\JsonApi;
|
use Tobyz\JsonApiServer\JsonApi;
|
||||||
|
use Tobyz\JsonApiServer\Schema\Type;
|
||||||
use Tobyz\Tests\JsonApiServer\AbstractTestCase;
|
use Tobyz\Tests\JsonApiServer\AbstractTestCase;
|
||||||
use Tobyz\Tests\JsonApiServer\MockAdapter;
|
use Tobyz\Tests\JsonApiServer\MockAdapter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see https://jsonapi.org/format/1.0/#fetching-pagination
|
* @see https://jsonapi.org/format/1.1/#fetching-pagination
|
||||||
* @todo Create a profile for offset pagination strategy
|
|
||||||
*/
|
*/
|
||||||
class OffsetPaginationTest extends AbstractTestCase
|
class OffsetPaginationTest extends AbstractTestCase
|
||||||
{
|
{
|
||||||
|
|
@ -26,60 +26,84 @@ class OffsetPaginationTest extends AbstractTestCase
|
||||||
*/
|
*/
|
||||||
private $api;
|
private $api;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var MockAdapter
|
|
||||||
*/
|
|
||||||
private $adapter;
|
|
||||||
|
|
||||||
public function setUp(): void
|
public function setUp(): void
|
||||||
{
|
{
|
||||||
$this->api = new JsonApi('http://example.com');
|
$this->api = new JsonApi('http://example.com');
|
||||||
|
|
||||||
$this->adapter = new MockAdapter();
|
$adapter = new MockAdapter(
|
||||||
|
array_map(function ($i) {
|
||||||
|
return (object) ['id' => (string) $i];
|
||||||
|
}, range(1, 100))
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->api->resourceType('articles', $adapter, function (Type $type) {
|
||||||
|
$type->paginate(20);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_can_request_limit_on_resource_collection()
|
public function test_can_request_limit_on_resource_collection()
|
||||||
{
|
{
|
||||||
$this->markTestIncomplete();
|
$response = $this->api->handle(
|
||||||
|
$this->buildRequest('GET', '/articles')
|
||||||
|
->withQueryParams(['page' => ['limit' => '10']])
|
||||||
|
);
|
||||||
|
|
||||||
|
$data = json_decode($response->getBody(), true)['data'] ?? null;
|
||||||
|
|
||||||
|
$this->assertCount(10, $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_can_request_offset_on_resource_collection()
|
public function test_can_request_offset_on_resource_collection()
|
||||||
{
|
{
|
||||||
$this->markTestIncomplete();
|
$response = $this->api->handle(
|
||||||
|
$this->buildRequest('GET', '/articles')
|
||||||
|
->withQueryParams(['page' => ['offset' => '5']])
|
||||||
|
);
|
||||||
|
|
||||||
|
$data = json_decode($response->getBody(), true)['data'] ?? null;
|
||||||
|
|
||||||
|
$this->assertEquals('6', $data[0]['id'] ?? null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_first_pagination_link_is_correct()
|
public function test_pagination_links_are_correct_and_retain_query_parameters()
|
||||||
{
|
{
|
||||||
$this->markTestIncomplete();
|
$response = $this->api->handle(
|
||||||
}
|
$this->buildRequest('GET', '/articles')
|
||||||
|
->withQueryParams([
|
||||||
|
'page' => ['offset' => '40'],
|
||||||
|
'otherParam' => 'value',
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
public function test_last_pagination_link_is_correct()
|
$links = json_decode($response->getBody(), true)['links'] ?? null;
|
||||||
{
|
|
||||||
$this->markTestIncomplete();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function test_next_pagination_link_is_correct()
|
$this->assertEquals('/articles?otherParam=value', $links['first'] ?? null);
|
||||||
{
|
$this->assertEquals('/articles?otherParam=value&page%5Boffset%5D=80', $links['last'] ?? null);
|
||||||
$this->markTestIncomplete();
|
$this->assertEquals('/articles?otherParam=value&page%5Boffset%5D=60', $links['next'] ?? null);
|
||||||
|
$this->assertEquals('/articles?otherParam=value&page%5Boffset%5D=20', $links['prev'] ?? null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_next_pagination_link_is_not_included_on_last_page()
|
public function test_next_pagination_link_is_not_included_on_last_page()
|
||||||
{
|
{
|
||||||
$this->markTestIncomplete();
|
$response = $this->api->handle(
|
||||||
|
$this->buildRequest('GET', '/articles')
|
||||||
|
->withQueryParams(['page' => ['offset' => '80']])
|
||||||
|
);
|
||||||
|
|
||||||
|
$links = json_decode($response->getBody(), true)['links'] ?? null;
|
||||||
|
|
||||||
|
$this->assertNull($links['next'] ?? null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_prev_pagination_link_is_correct()
|
public function test_prev_pagination_link_is_not_included_on_first_page()
|
||||||
{
|
{
|
||||||
$this->markTestIncomplete();
|
$response = $this->api->handle(
|
||||||
}
|
$this->buildRequest('GET', '/articles')
|
||||||
|
->withQueryParams(['page' => ['offset' => '0']])
|
||||||
|
);
|
||||||
|
|
||||||
public function test_prev_pagination_link_is_not_included_on_last_page()
|
$links = json_decode($response->getBody(), true)['links'] ?? null;
|
||||||
{
|
|
||||||
$this->markTestIncomplete();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function test_pagination_links_retain_other_query_parameters()
|
$this->assertNull($links['prev'] ?? null);
|
||||||
{
|
|
||||||
$this->markTestIncomplete();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,12 @@
|
||||||
namespace Tobyz\Tests\JsonApiServer\specification;
|
namespace Tobyz\Tests\JsonApiServer\specification;
|
||||||
|
|
||||||
use Tobyz\JsonApiServer\JsonApi;
|
use Tobyz\JsonApiServer\JsonApi;
|
||||||
|
use Tobyz\JsonApiServer\Schema\Type;
|
||||||
use Tobyz\Tests\JsonApiServer\AbstractTestCase;
|
use Tobyz\Tests\JsonApiServer\AbstractTestCase;
|
||||||
use Tobyz\Tests\JsonApiServer\MockAdapter;
|
use Tobyz\Tests\JsonApiServer\MockAdapter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see https://jsonapi.org/format/1.0/#fetching-sparse-fieldsets
|
* @see https://jsonapi.org/format/1.1/#fetching-sparse-fieldsets
|
||||||
*/
|
*/
|
||||||
class SparseFieldsetsTest extends AbstractTestCase
|
class SparseFieldsetsTest extends AbstractTestCase
|
||||||
{
|
{
|
||||||
|
|
@ -25,40 +26,56 @@ class SparseFieldsetsTest extends AbstractTestCase
|
||||||
*/
|
*/
|
||||||
private $api;
|
private $api;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var MockAdapter
|
|
||||||
*/
|
|
||||||
private $adapter;
|
|
||||||
|
|
||||||
public function setUp(): void
|
public function setUp(): void
|
||||||
{
|
{
|
||||||
$this->api = new JsonApi('http://example.com');
|
$this->api = new JsonApi('http://example.com');
|
||||||
|
|
||||||
$this->adapter = new MockAdapter();
|
$articlesAdapter = new MockAdapter([
|
||||||
|
'1' => (object) [
|
||||||
|
'id' => '1',
|
||||||
|
'title' => 'foo',
|
||||||
|
'body' => 'bar',
|
||||||
|
'user' => (object) [
|
||||||
|
'id' => '1',
|
||||||
|
'firstName' => 'Toby',
|
||||||
|
'lastName' => 'Zerner',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->api->resourceType('articles', $articlesAdapter, function (Type $type) {
|
||||||
|
$type->attribute('title');
|
||||||
|
$type->attribute('body');
|
||||||
|
$type->hasOne('user')->includable();
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->api->resourceType('users', new MockAdapter(), function (Type $type) {
|
||||||
|
$type->attribute('firstName');
|
||||||
|
$type->attribute('lastName');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_can_request_sparse_fieldsets_for_a_type()
|
public function test_can_request_sparse_fieldsets()
|
||||||
{
|
{
|
||||||
$this->markTestIncomplete();
|
$request = $this->api->handle(
|
||||||
}
|
$this->buildRequest('GET', '/articles/1')
|
||||||
|
->withQueryParams([
|
||||||
|
'include' => 'user',
|
||||||
|
'fields' => [
|
||||||
|
'articles' => 'title,user',
|
||||||
|
'users' => 'firstName',
|
||||||
|
],
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
public function test_can_request_sparse_fieldsets_for_multiple_types()
|
$document = json_decode($request->getBody(), true);
|
||||||
{
|
|
||||||
$this->markTestIncomplete();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function test_can_request_sparse_fieldsets_on_resource_collections()
|
$article = $document['data']['attributes'] ?? [];
|
||||||
{
|
$user = $document['included'][0]['attributes'] ?? [];
|
||||||
$this->markTestIncomplete();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function test_can_request_sparse_fieldsets_on_create()
|
$this->assertArrayHasKey('title', $article);
|
||||||
{
|
$this->assertArrayNotHasKey('body', $article);
|
||||||
$this->markTestIncomplete();
|
$this->assertArrayHasKey('firstName', $user);
|
||||||
}
|
$this->assertArrayNotHasKey('lastName', $user);
|
||||||
|
|
||||||
public function test_can_request_sparse_fieldsets_on_update()
|
|
||||||
{
|
|
||||||
$this->markTestIncomplete();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,16 @@
|
||||||
|
|
||||||
namespace Tobyz\Tests\JsonApiServer\specification;
|
namespace Tobyz\Tests\JsonApiServer\specification;
|
||||||
|
|
||||||
|
use Tobyz\JsonApiServer\Exception\BadRequestException;
|
||||||
|
use Tobyz\JsonApiServer\Exception\ConflictException;
|
||||||
|
use Tobyz\JsonApiServer\Exception\ResourceNotFoundException;
|
||||||
use Tobyz\JsonApiServer\JsonApi;
|
use Tobyz\JsonApiServer\JsonApi;
|
||||||
|
use Tobyz\JsonApiServer\Schema\Type;
|
||||||
use Tobyz\Tests\JsonApiServer\AbstractTestCase;
|
use Tobyz\Tests\JsonApiServer\AbstractTestCase;
|
||||||
use Tobyz\Tests\JsonApiServer\MockAdapter;
|
use Tobyz\Tests\JsonApiServer\MockAdapter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see https://jsonapi.org/format/1.0/#crud-updating
|
* @see https://jsonapi.org/format/1.1/#crud-updating
|
||||||
*/
|
*/
|
||||||
class UpdatingResourcesTest extends AbstractTestCase
|
class UpdatingResourcesTest extends AbstractTestCase
|
||||||
{
|
{
|
||||||
|
|
@ -25,60 +29,122 @@ class UpdatingResourcesTest extends AbstractTestCase
|
||||||
*/
|
*/
|
||||||
private $api;
|
private $api;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var MockAdapter
|
|
||||||
*/
|
|
||||||
private $adapter;
|
|
||||||
|
|
||||||
public function setUp(): void
|
public function setUp(): void
|
||||||
{
|
{
|
||||||
$this->api = new JsonApi('http://example.com');
|
$this->api = new JsonApi('http://example.com');
|
||||||
|
|
||||||
$this->adapter = new MockAdapter();
|
$adapter = new MockAdapter([
|
||||||
|
'1' => (object) ['id' => '1', 'name' => 'initial'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->api->resourceType('users', $adapter, function (Type $type) {
|
||||||
|
$type->updatable();
|
||||||
|
$type->attribute('name')->writable();
|
||||||
|
$type->hasOne('pet')->writable();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_bad_request_error_if_body_does_not_contain_data_type_and_id()
|
public function test_bad_request_error_if_body_does_not_contain_data_type_and_id()
|
||||||
{
|
{
|
||||||
$this->markTestIncomplete();
|
$this->expectException(BadRequestException::class);
|
||||||
}
|
|
||||||
|
|
||||||
public function test_only_included_attributes_are_processed()
|
$this->api->handle(
|
||||||
{
|
$this->buildRequest('PATCH', '/users/1')
|
||||||
$this->markTestIncomplete();
|
->withParsedBody([
|
||||||
}
|
'data' => [],
|
||||||
|
])
|
||||||
public function test_only_included_relationships_are_processed()
|
);
|
||||||
{
|
|
||||||
$this->markTestIncomplete();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_bad_request_error_if_relationship_does_not_contain_data()
|
public function test_bad_request_error_if_relationship_does_not_contain_data()
|
||||||
{
|
{
|
||||||
$this->markTestIncomplete();
|
$this->expectException(BadRequestException::class);
|
||||||
|
|
||||||
|
$this->api->handle(
|
||||||
|
$this->buildRequest('PATCH', '/users/1')
|
||||||
|
->withParsedBody([
|
||||||
|
'data' => [
|
||||||
|
'type' => 'users',
|
||||||
|
'id' => '1',
|
||||||
|
'relationships' => [
|
||||||
|
'pet' => [],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_ok_response_if_resource_successfully_updated()
|
public function test_ok_response_with_updated_data_if_resource_successfully_updated()
|
||||||
{
|
{
|
||||||
$this->markTestIncomplete();
|
$response = $this->api->handle(
|
||||||
}
|
$this->buildRequest('PATCH', '/users/1')
|
||||||
|
->withParsedBody([
|
||||||
|
'data' => [
|
||||||
|
'type' => 'users',
|
||||||
|
'id' => '1',
|
||||||
|
'attributes' => [
|
||||||
|
'name' => 'updated'
|
||||||
|
],
|
||||||
|
],
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
public function test_ok_response_includes_updated_data()
|
$document = json_decode($response->getBody(), true);
|
||||||
{
|
|
||||||
$this->markTestIncomplete();
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
|
$this->assertEquals('updated', $document['data']['attributes']['name'] ?? null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_not_found_error_if_resource_does_not_exist()
|
public function test_not_found_error_if_resource_does_not_exist()
|
||||||
{
|
{
|
||||||
$this->markTestIncomplete();
|
$this->expectException(ResourceNotFoundException::class);
|
||||||
|
|
||||||
|
$this->api->handle(
|
||||||
|
$this->buildRequest('PATCH', '/users/404')
|
||||||
|
->withParsedBody([
|
||||||
|
'data' => [
|
||||||
|
'type' => 'users',
|
||||||
|
'id' => '404',
|
||||||
|
'attributes' => [
|
||||||
|
'name' => 'bob',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_not_found_error_if_references_resource_that_does_not_exist()
|
public function test_not_found_error_if_references_resource_that_does_not_exist()
|
||||||
{
|
{
|
||||||
$this->markTestIncomplete();
|
$this->expectException(ResourceNotFoundException::class);
|
||||||
|
|
||||||
|
$this->api->handle(
|
||||||
|
$this->buildRequest('PATCH', '/users/1')
|
||||||
|
->withParsedBody([
|
||||||
|
'data' => [
|
||||||
|
'type' => 'users',
|
||||||
|
'id' => '1',
|
||||||
|
'relationships' => [
|
||||||
|
'pet' => [
|
||||||
|
'data' => ['type' => 'pets', 'id' => '1'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_conflict_error_if_type_and_id_does_not_match_endpoint()
|
public function test_conflict_error_if_type_and_id_does_not_match_endpoint()
|
||||||
{
|
{
|
||||||
$this->markTestIncomplete();
|
$this->expectException(ConflictException::class);
|
||||||
|
|
||||||
|
$this->api->handle(
|
||||||
|
$this->buildRequest('PATCH', '/users/1')
|
||||||
|
->withParsedBody([
|
||||||
|
'data' => [
|
||||||
|
'type' => 'pets',
|
||||||
|
'id' => '1',
|
||||||
|
],
|
||||||
|
])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue