diff --git a/composer.json b/composer.json index 8ce2d74..0decca3 100644 --- a/composer.json +++ b/composer.json @@ -28,6 +28,6 @@ } }, "scripts": { - "test": "php-cs-fixer fix -v --dry-run --ansi && phpunit --colors=always" + "test": "php-cs-fixer fix -v --dry-run --ansi && phpunit --colors=always --coverage-text" } } diff --git a/src/Document/Resource/Relationship/Relationship.php b/src/Document/Resource/Relationship.php similarity index 93% rename from src/Document/Resource/Relationship/Relationship.php rename to src/Document/Resource/Relationship.php index 05b4226..49cefcb 100644 --- a/src/Document/Resource/Relationship/Relationship.php +++ b/src/Document/Resource/Relationship.php @@ -1,14 +1,13 @@ isReservedName($name)) { - throw new \InvalidArgumentException('Can not use a reserved name'); + throw new \InvalidArgumentException("Can not use a reserved name '$name'"); } if (!$this->isValidMemberName($name)) { - throw new \OutOfBoundsException('Not a valid attribute name'); + throw new \OutOfBoundsException("Not a valid attribute name '$name'"); } if (isset($this->relationships[$name])) { - throw new \LogicException("Field $name already exists in relationships"); + throw new \LogicException("Field '$name' already exists in relationships"); } $this->attributes[$name] = $value; } @@ -49,10 +48,13 @@ public function setAttribute(string $name, $value) public function setRelationship(string $name, Relationship $relationship) { if ($this->isReservedName($name)) { - throw new \InvalidArgumentException('Can not use a reserved name'); + throw new \InvalidArgumentException("Can not use a reserved name '$name'"); + } + if (!$this->isValidMemberName($name)) { + throw new \OutOfBoundsException("Not a valid attribute name '$name'"); } if (isset($this->attributes[$name])) { - throw new \LogicException("Field $name already exists in attributes"); + throw new \LogicException("Field '$name' already exists in attributes"); } $this->relationships[$name] = $relationship; } diff --git a/test/Document/CompoundDocumentTest.php b/test/Document/CompoundDocumentTest.php index b22ef66..c796508 100644 --- a/test/Document/CompoundDocumentTest.php +++ b/test/Document/CompoundDocumentTest.php @@ -7,7 +7,7 @@ use JsonApiPhp\JsonApi\Document\Meta; use JsonApiPhp\JsonApi\Document\Resource\Linkage\MultiLinkage; use JsonApiPhp\JsonApi\Document\Resource\Linkage\SingleLinkage; -use JsonApiPhp\JsonApi\Document\Resource\Relationship\Relationship; +use JsonApiPhp\JsonApi\Document\Resource\Relationship; use JsonApiPhp\JsonApi\Document\Resource\ResourceIdentifier; use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; use JsonApiPhp\JsonApi\Test\BaseTestCase; @@ -159,14 +159,26 @@ public function testOfficialDocsExample() /** * @expectedException \LogicException * @expectedExceptionMessage Full linkage is required for apples:1 + * @dataProvider documentsWithoutFullLinkage + * @param Document $doc */ - public function testFullLinkageIsRequired() + public function testFullLinkageIsRequired(Document $doc) { - $doc = Document::nullDocument(); $doc->setIncluded(new ResourceObject('apples', '1')); json_encode($doc); } + public function documentsWithoutFullLinkage(): array + { + return [ + [Document::nullDocument()], + [Document::fromIdentifier(new ResourceIdentifier('oranges', '1'))], + [Document::fromIdentifiers(new ResourceIdentifier('oranges', '1'), new ResourceIdentifier('oranges', '2'))], + [Document::fromResource(new ResourceObject('oranges', '1'))], + [Document::fromResources(new ResourceObject('oranges', '1'), new ResourceObject('oranges', '1'))], + ]; + } + /** * A compound document must be explicitly marked as sparse. In this case full linkage is not required. */ diff --git a/test/Document/DocumentTest.php b/test/Document/DocumentTest.php index 2d255ee..72e6336 100644 --- a/test/Document/DocumentTest.php +++ b/test/Document/DocumentTest.php @@ -11,7 +11,7 @@ use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; use JsonApiPhp\JsonApi\Test\BaseTestCase; use JsonApiPhp\JsonApi\Test\Document\Resource\Relationship\LinkageTest; -use JsonApiPhp\JsonApi\Test\Document\Resource\ResourceTest; +use JsonApiPhp\JsonApi\Test\Document\Resource\ResourceObjectTest; /** * This is the JSON document's top level object @@ -68,7 +68,7 @@ public function testDocumentMayContainJustErrors() /** * A valid document may contain just a primary data object. - * The primary data object is represented by ResourceInterface (@see ResourceTest for details). + * The primary data object is represented by ResourceInterface (@see ResourceObjectTest for details). * Here is how a document can be created from different kinds of resources: * - null resource * - resource identifier diff --git a/test/Document/Resource/Relationship/RelationshipTest.php b/test/Document/Resource/Relationship/RelationshipTest.php index 8b98734..6fdfe10 100644 --- a/test/Document/Resource/Relationship/RelationshipTest.php +++ b/test/Document/Resource/Relationship/RelationshipTest.php @@ -6,8 +6,8 @@ use JsonApiPhp\JsonApi\Document\Link\Link; use JsonApiPhp\JsonApi\Document\Meta; use JsonApiPhp\JsonApi\Document\Resource\Linkage\NullLinkage; +use JsonApiPhp\JsonApi\Document\Resource\Relationship; use JsonApiPhp\JsonApi\Document\Resource\Relationship\Linkage; -use JsonApiPhp\JsonApi\Document\Resource\Relationship\Relationship; use JsonApiPhp\JsonApi\Test\BaseTestCase; /** @@ -52,7 +52,7 @@ public function testCanCreateFromSelfLink() } } ', - Relationship::fromSelfLink(new Link('http://localhost')) + \JsonApiPhp\JsonApi\Document\Resource\Relationship::fromSelfLink(new Link('http://localhost')) ); } @@ -66,7 +66,7 @@ public function testCanCreateFromRelatedLink() } } ', - Relationship::fromRelatedLink(new Link('http://localhost')) + \JsonApiPhp\JsonApi\Document\Resource\Relationship::fromRelatedLink(new Link('http://localhost')) ); } @@ -78,7 +78,7 @@ public function testCanCreateFromLinkage() "data": null } ', - Relationship::fromLinkage(new NullLinkage()) + \JsonApiPhp\JsonApi\Document\Resource\Relationship::fromLinkage(new NullLinkage()) ); } diff --git a/test/Document/Resource/ResourceFieldsTest.php b/test/Document/Resource/ResourceFieldsTest.php index c301fb3..f8aede2 100644 --- a/test/Document/Resource/ResourceFieldsTest.php +++ b/test/Document/Resource/ResourceFieldsTest.php @@ -4,7 +4,7 @@ namespace JsonApiPhp\JsonApi\Test\Document\Resource; use JsonApiPhp\JsonApi\Document\Meta; -use JsonApiPhp\JsonApi\Document\Resource\Relationship\Relationship; +use JsonApiPhp\JsonApi\Document\Resource\Relationship; use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; use PHPUnit\Framework\TestCase; @@ -23,7 +23,7 @@ class ResourceFieldsTest extends TestCase { /** * @expectedException \LogicException - * @expectedExceptionMessage Field foo already exists in attributes + * @expectedExceptionMessage Field 'foo' already exists in attributes */ public function testCanNotSetRelationshipIfAttributeExists() { @@ -34,7 +34,7 @@ public function testCanNotSetRelationshipIfAttributeExists() /** * @expectedException \LogicException - * @expectedExceptionMessage Field foo already exists in relationships + * @expectedExceptionMessage Field 'foo' already exists in relationships */ public function testCanNotSetAttributeIfRelationshipExists() { @@ -67,29 +67,6 @@ public function testRelationshipCanNotHaveReservedNames(string $name) $res->setRelationship($name, Relationship::fromMeta(Meta::fromArray(['a' => 'b']))); } - /** - * @param string $name - * @expectedException \OutOfBoundsException - * @expectedExceptionMessage Not a valid attribute name - * @dataProvider invalidAttributeNames - */ - public function testAttributeNameIsNotValid(string $name) - { - $res = new ResourceObject('books', 'abc'); - $res->setAttribute($name, 1); - } - - /** - * @param string $name - * @dataProvider validAttributeNames - */ - public function testAttributeNameIsValid(string $name) - { - $res = new ResourceObject('books', 'abc'); - $res->setAttribute($name, 1); - $this->assertTrue(true); - } - public function reservedAttributeNames(): array { return [ @@ -97,35 +74,4 @@ public function reservedAttributeNames(): array ['type'], ]; } - - public function invalidAttributeNames(): array - { - return [ - ['_abcde'], - ['abcd_'], - ['abc$EDS'], - ['#abcde'], - ['abcde('], - ['b_'], - ['_a'], - ['$ab_c-d'], - ['-abc'], - ]; - } - - public function validAttributeNames(): array - { - return [ - ['abcd'], - ['abcA4C'], - ['abc_d3f45'], - ['abd_eca'], - ['a'], - ['b'], - ['ab'], - ['a-bc_de'], - ['abcéêçèÇ_n'], - ['abc 汉字 abc'], - ]; - } } diff --git a/test/Document/Resource/ResourceIdentifierTest.php b/test/Document/Resource/ResourceIdentifierTest.php new file mode 100644 index 0000000..bc1c7f0 --- /dev/null +++ b/test/Document/Resource/ResourceIdentifierTest.php @@ -0,0 +1,45 @@ +assertEncodesTo( + ' + { + "type": "books", + "id": "1" + } + ', + new ResourceIdentifier('books', '1') + ); + } + + public function testResourceIdentifierMayContainMeta() + { + $this->assertEncodesTo( + ' + { + "type": "books", + "id": "1", + "meta": { + "foo":"bar" + } + } + ', + new ResourceIdentifier('books', '1', Meta::fromArray(['foo' => 'bar'])) + ); + } +} diff --git a/test/Document/Resource/ResourceObjectTest.php b/test/Document/Resource/ResourceObjectTest.php new file mode 100644 index 0000000..8dfdb37 --- /dev/null +++ b/test/Document/Resource/ResourceObjectTest.php @@ -0,0 +1,111 @@ +assertEncodesTo('{"type": "books"}', new ResourceObject('books')); + } + + /** + * In addition, a resource object MAY contain any of these top-level members: + * + * - attributes: an attributes object representing some of the resource’s data. + * + * - relationships: a relationships object describing relationships + * between the resource and other JSON API resources. + * + * - links: a links object containing links related to the resource. + * + * - meta: a meta object containing non-standard meta-information about a resource + * that can not be represented as an attribute or relationship. + */ + public function testResourceObjectMayContainAttributes() + { + $apple = new ResourceObject('apples', '1'); + $apple->setAttribute('color', 'red'); + $this->assertEncodesTo('{"type":"apples", "id":"1", "attributes":{"color":"red"}}', $apple); + } + + public function testResourceObjectMayContainRelationships() + { + $article = new ResourceObject('articles', '1'); + $user = new ResourceIdentifier('users', '42'); + $article->setRelationship('author', Relationship::fromLinkage(new SingleLinkage($user))); + $this->assertEncodesTo( + ' + { + "type":"articles", + "id":"1", + "relationships":{ + "author":{ + "data":{ + "type":"users", + "id":"42" + } + } + } + } + ', + $article + ); + } + + public function testResourceObjectMayContainLinks() + { + $article = new ResourceObject('articles', '1'); + $article->setLink('self', 'https://example.com'); + $this->assertEncodesTo( + ' + { + "type":"articles", + "id":"1", + "links":{ + "self":"https://example.com" + } + } + ', + $article + ); + } + + public function testResourceObjectMayContainMeta() + { + $article = new ResourceObject('articles', '1'); + $article->setMeta(Meta::fromArray(['tags' => ['cool', 'new']])); + $this->assertEncodesTo( + ' + { + "type":"articles", + "id":"1", + "meta":{ + "tags":[ + "cool", + "new" + ] + } + } + ', + $article + ); + } +} diff --git a/test/Document/Resource/ResourceTest.php b/test/Document/Resource/ResourceTest.php deleted file mode 100644 index eeb3e7e..0000000 --- a/test/Document/Resource/ResourceTest.php +++ /dev/null @@ -1,86 +0,0 @@ -assertEncodesTo($expected, $data); - } - - public function resourceProvider() - { - return [ - [ - '{"type": "books"}', - new ResourceObject('books'), - ], - [ - '{"type":"books","id":"42abc"}', - new ResourceIdentifier('books', '42abc'), - ], - [ - ' - { - "type": "books", - "id": "42abc", - "meta": { - "foo":"bar" - } - } - ', - new ResourceIdentifier('books', '42abc', Meta::fromArray(['foo' => 'bar'])), - ], - [ - ' - { - "type": "books", - "id": "42abc", - "attributes": { - "attr": "val" - }, - "relationships": { - "author": { - "meta": { - "a": "b" - } - } - }, - "links": { - "self": "http://localhost" - }, - "meta": { - "foo": "bar" - } - } - ', - (function () { - $resource = new ResourceObject('books', '42abc'); - $resource->setMeta(Meta::fromArray(['foo' => 'bar'])); - $resource->setAttribute('attr', 'val'); - $resource->setLink('self', 'http://localhost'); - $resource->setRelationship('author', Relationship::fromMeta(Meta::fromArray(['a' => 'b']))); - return $resource; - })(), - ], - ]; - } -} diff --git a/test/IntegrationTest.php b/test/IntegrationTest.php index 12b189d..a884d86 100644 --- a/test/IntegrationTest.php +++ b/test/IntegrationTest.php @@ -5,7 +5,7 @@ use JsonApiPhp\JsonApi\Document; use JsonApiPhp\JsonApi\Document\Resource\Linkage\SingleLinkage; -use JsonApiPhp\JsonApi\Document\Resource\Relationship\Relationship; +use JsonApiPhp\JsonApi\Document\Resource\Relationship; use JsonApiPhp\JsonApi\Document\Resource\ResourceIdentifier; use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; use PHPUnit\Framework\TestCase; diff --git a/test/MemberNamesTest.php b/test/MemberNamesTest.php new file mode 100644 index 0000000..d7c405f --- /dev/null +++ b/test/MemberNamesTest.php @@ -0,0 +1,89 @@ +setAttribute($name, 1); + } + + /** + * @param string $name + * @dataProvider validAttributeNames + */ + public function testValidAttributeNamesCanBeSet(string $name) + { + $res = new ResourceObject('books', 'abc'); + $res->setAttribute($name, 1); + $this->assertInternalType('string', json_encode($res)); + } + + /** + * @param string $name + * @expectedException \OutOfBoundsException + * @expectedExceptionMessage Not a valid attribute name + * @dataProvider invalidAttributeNames + */ + public function testInvalidRelationshipNamesAreNotAllowed(string $name) + { + $res = new ResourceObject('books', 'abc'); + $res->setRelationship($name, Relationship::fromSelfLink(new Link('https://example.com'))); + } + + /** + * @param string $name + * @dataProvider validAttributeNames + */ + public function testValidRelationshipNamesCanBeSet(string $name) + { + $res = new ResourceObject('books', 'abc'); + $res->setRelationship($name, Relationship::fromSelfLink(new Link('https://example.com'))); + $this->assertInternalType('string', json_encode($res)); + } + + public function invalidAttributeNames(): array + { + return [ + ['_abcde'], + ['abcd_'], + ['abc$EDS'], + ['#abcde'], + ['abcde('], + ['b_'], + ['_a'], + ['$ab_c-d'], + ['-abc'], + ]; + } + + public function validAttributeNames(): array + { + return [ + ['abcd'], + ['abcA4C'], + ['abc_d3f45'], + ['abd_eca'], + ['a'], + ['b'], + ['ab'], + ['a-bc_de'], + ['abcéêçèÇ_n'], + ['abc 汉字 abc'], + ]; + } +}