From 486b0c8d843fc934dfc796f863a705611a065a71 Mon Sep 17 00:00:00 2001 From: Antony Videtta Date: Thu, 23 Jan 2020 14:14:02 +1300 Subject: [PATCH 1/6] Test automation for jsondata improvement --- tests/unit/JSONDataFormatterTest.php | 54 +++++++++++++++++----------- tests/unit/JSONDataFormatterTest.yml | 24 ++++--------- 2 files changed, 40 insertions(+), 38 deletions(-) diff --git a/tests/unit/JSONDataFormatterTest.php b/tests/unit/JSONDataFormatterTest.php index fe280b5..60681fe 100644 --- a/tests/unit/JSONDataFormatterTest.php +++ b/tests/unit/JSONDataFormatterTest.php @@ -3,16 +3,15 @@ namespace SilverStripe\RestfulServer\Tests; use SilverStripe\RestfulServer\RestfulServer; +use SilverStripe\ORM\DataObject; use SilverStripe\RestfulServer\Tests\Stubs\JSONDataFormatterTypeTestObject; +use SilverStripe\Core\Config\Config; use SilverStripe\Dev\SapphireTest; use SilverStripe\RestfulServer\DataFormatter\JSONDataFormatter; /** - * - * @todo Test Relation getters - * @todo Test filter and limit through GET params - * @todo Test DELETE verb - * + * Tests improvements made to JsonTypes, + * calls method which appends more fields */ class JSONDataFormatterTest extends SapphireTest { @@ -22,25 +21,40 @@ class JSONDataFormatterTest extends SapphireTest JSONDataFormatterTypeTestObject::class, ]; + protected $usesDatabase = true; + public function testJSONTypes() { + + // Needed as private static $api_access = true; doesn't seem to work on the stub file + Config::inst()->update(JSONDataFormatterTypeTestObject::class, 'api_access', true); + + // Grab test object $formatter = new JSONDataFormatter(); $parent = $this->objFromFixture(JSONDataFormatterTypeTestObject::class, 'parent'); - $json = $formatter->convertDataObject($parent); - $this->assertRegexp('/"ID":\d+/', $json, 'PK casted to integer'); - $this->assertRegexp('/"Created":"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}"/', $json, 'Datetime casted to string'); - $this->assertContains('"Name":"Parent"', $json, 'String casted to string'); - $this->assertContains('"Active":true', $json, 'Boolean casted to boolean'); - $this->assertContains('"Sort":17', $json, 'Integer casted to integer'); - $this->assertContains('"Average":1.2345', $json, 'Float casted to float'); - $this->assertContains('"ParentID":0', $json, 'Empty FK is 0'); + $json = json_decode($formatter->convertDataObject($parent)); - $child3 = $this->objFromFixture(JSONDataFormatterTypeTestObject::class, 'child3'); - $json = $formatter->convertDataObject($child3); - $this->assertContains('"Name":null', $json, 'Empty string is null'); - $this->assertContains('"Active":false', $json, 'Empty boolean is false'); - $this->assertContains('"Sort":0', $json, 'Empty integer is 0'); - $this->assertContains('"Average":0', $json, 'Empty float is 0'); - $this->assertRegexp('/"ParentID":\d+/', $json, 'FK casted to integer'); + // Returns valid array and isn't null + $this->assertNotEmpty($json, 'Array is empty'); + + // Test that original fields still exist, ie id, href, and className + $standard_id = $json->Children[0]->id; + $standard_className = $json->Children[0]->className; + $standard_href = $json->Children[0]->href; + + $this->assertEquals(8, $standard_id, "Standard id field not equal"); + $this->assertEquals('SilverStripe\RestfulServer\Tests\Stubs\JSONDataFormatterTypeTestObject', $standard_className, "Standard className does not equal"); + $this->assertEquals('http://localhost/api/v1/SilverStripe-RestfulServer-Tests-Stubs-JSONDataFormatterTypeTestObject/8.json', $standard_href, "Standard href field not equal"); + + // Test method improvements, more fields rather than just id, href, className + $this->assertEquals(9, $json->ID, "ID not equal"); + $this->assertEquals("SilverStripe\\RestfulServer\\Tests\\Stubs\\JSONDataFormatterTypeTestObject", $json->ClassName, "Class not equal"); + $this->assertEquals(date('Y-m-d H:i:s', time() - 1), $json->LastEdited, "Last edited does not equal"); + $this->assertEquals(date('Y-m-d H:i:s', time() - 1), $json->Created, "Created at does not equal"); + $this->assertEquals("Test Object", $json->Name, "Name not equal"); + $this->assertEquals(false, $json->Active, "Active not equal to false"); + $this->assertEquals(0, $json->Sort, "Sort not equal to 0"); + $this->assertEquals(0, $json->Average, "Average not equal to 0"); + $this->assertEquals(0, $json->ParentID, "ParentID not equal to 0"); } } diff --git a/tests/unit/JSONDataFormatterTest.yml b/tests/unit/JSONDataFormatterTest.yml index 4c6f2f8..38f2b2a 100644 --- a/tests/unit/JSONDataFormatterTest.yml +++ b/tests/unit/JSONDataFormatterTest.yml @@ -1,20 +1,8 @@ SilverStripe\RestfulServer\Tests\Stubs\JSONDataFormatterTypeTestObject: + foo: + ID: 8 + Name: Test Object 1 parent: - Name: Parent - Active: true - Sort: 17 - Average: 1.2345 - child1: - Name: Child 1 - Active: 1 - Sort: 4 - Average: 6.78 - Parent: =>SilverStripe\RestfulServer\Tests\Stubs\JSONDataFormatterTypeTestObject.parent - child2: - Name: Child 2 - Active: false - Sort: 9 - Average: 1 - Parent: =>SilverStripe\RestfulServer\Tests\Stubs\JSONDataFormatterTypeTestObject.parent - child3: - Parent: =>SilverStripe\RestfulServer\Tests\Stubs\JSONDataFormatterTypeTestObject.parent + ID: 9 + Name: Test Object + Children: =>SilverStripe\RestfulServer\Tests\Stubs\JSONDataFormatterTypeTestObject.foo From a730db51e21c26e311e855159a994e294dca2b9a Mon Sep 17 00:00:00 2001 From: Antony Videtta Date: Thu, 23 Jan 2020 14:38:46 +1300 Subject: [PATCH 2/6] Add modification to JsonDataFormatter --- src/DataFormatter/JSONDataFormatter.php | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/DataFormatter/JSONDataFormatter.php b/src/DataFormatter/JSONDataFormatter.php index 0ff743d..fc656ef 100644 --- a/src/DataFormatter/JSONDataFormatter.php +++ b/src/DataFormatter/JSONDataFormatter.php @@ -10,6 +10,8 @@ use SilverStripe\ORM\DataObjectInterface; use SilverStripe\Control\Director; use SilverStripe\ORM\SS_List; use SilverStripe\ORM\FieldType; +use SilverStripe\Core\ClassInfo; +use SilverStripe\ORM\DataObject; /** * Formats a DataObject's member fields into a JSON string @@ -118,10 +120,16 @@ class JSONDataFormatter extends DataFormatter ? $this->sanitiseClassName($relClass) . '/' . $obj->$fieldName : $this->sanitiseClassName($className) . "/$id/$relName"; $href = Director::absoluteURL($rel); - $serobj->$relName = ArrayData::array_to_object(array( + $component = $obj->getField($relName); + $baseFields = [ "className" => $relClass, "href" => "$href.json", - "id" => self::cast($obj->obj($fieldName)) + "id" => self::cast($obj->obj($fieldName)), + ]; + + $serobj->$relName = ArrayData::array_to_object(array_replace( + $baseFields, + ClassInfo::hasMethod($component, 'getApiFields') ? (array) $component->getApiFields($baseFields) : [] )); } @@ -152,10 +160,14 @@ class JSONDataFormatter extends DataFormatter } $rel = $this->config()->api_base . $this->sanitiseClassName($relClass) . "/$item->ID"; $href = Director::absoluteURL($rel); - $innerParts[] = ArrayData::array_to_object(array( + $baseFields = [ "className" => $relClass, "href" => "$href.json", "id" => $item->ID + ]; + $innerParts[] = ArrayData::array_to_object(array_replace( + $baseFields, + ClassInfo::hasMethod($item, 'getApiFields') ? (array) $item->getApiFields($baseFields) : [] )); } $serobj->$relName = $innerParts; From 814cd5e12431dc1637a734816d4b4ed5f18801ec Mon Sep 17 00:00:00 2001 From: Antony Videtta Date: Thu, 23 Jan 2020 14:44:13 +1300 Subject: [PATCH 3/6] Modifications to readme --- README.md | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 65 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 5e4a321..8a287a4 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ ## Overview This class gives your application a RESTful API. All you have to do is set the `api_access` configuration option to `true` -on the appropriate DataObjects. You will need to ensure that all of your data manipulation and security is defined in -your model layer (ie, the DataObject classes) and not in your Controllers. This is the recommended design for SilverStripe +on the appropriate `DataObject`. You will need to ensure that all of your data manipulation and security is defined in +your model layer (ie, the `DataObject` classes) and not in your Controllers. This is the recommended design for SilverStripe applications. ## Requirements @@ -20,7 +20,7 @@ For a SilverStripe 3.x compatible version of this module, please see the [1.0 br ## Configuration -Example DataObject with simple API access, giving full access to all object properties and relations, +Example `DataObject` with simple API access, giving full access to all object properties and relations, unless explicitly controlled through model permissions. ```php @@ -39,7 +39,7 @@ class Article extends DataObject { } ``` -Example DataObject with advanced API access, limiting viewing and editing to Title attribute only: +Example `DataObject` with advanced API access, limiting viewing and editing to the "Title" attribute only: ```php namespace Vendor\Project; @@ -60,7 +60,7 @@ class Article extends DataObject { } ``` -Example DataObject field mapping, allows aliasing fields so that public requests and responses display different field names: +Example `DataObject` field mapping, allows aliasing fields so that public requests and responses display different field names: ```php namespace Vendor\Project; @@ -83,7 +83,66 @@ class Article extends DataObject { ]; } ``` -Given a dataobject with values: + +Example `DataObject` `HasMany` and `ManyMany` field-display handling. Only available on `JSONDataFormatter`. Declaring a `getApiFields` method in your `DataObject` (or an `Extension` subclass) allows additional fields to be shown on those relations, in addition to "id", "className" and "href": + +```php +namespace Vendor\Project; + +use SilverStripe\ORM\DataObject; + +class Article extends DataObject { + + private static $db = [ + 'Title'=>'Text', + 'Published'=>'Boolean' + ]; + + private static $api_access = true; + + /** + * @param array $baseFields + * @return array + */ + public function getApiFields($baseFields) + { + return [ + 'Title' => $this->Title, + ]; + } +} +``` + +Example `DataObject` `HasMany` and `ManyMany` field-display handling. Only available on `JSONDataFormatter`. Declaring a `getApiFields` method in your `DataObject` (or an `Extension` subclass) allows existing fields that the formatter returns (like "id", "className" and "href"), to be overloaded: + +```php +namespace Vendor\Project; + +use SilverStripe\ORM\DataObject; + +class Article extends DataObject { + + private static $db = [ + 'Title'=>'Text', + 'Published'=>'Boolean' + ]; + + private static $api_access = true; + + /** + * @param array $baseFields + * @return array + */ + public function getApiFields($baseFields) + { + return [ + 'href' => $this->myHrefOverrideMethod($baseFields['href']), + ]; + } +} +``` + +Given a `DataObject` with values: ```yml ID: 12 Title: Title Value From a2f0724e8f480e892222bcb93898be31e1c13685 Mon Sep 17 00:00:00 2001 From: Antony Videtta Date: Thu, 23 Jan 2020 14:49:24 +1300 Subject: [PATCH 4/6] Remove timestamp fields --- tests/unit/JSONDataFormatterTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/unit/JSONDataFormatterTest.php b/tests/unit/JSONDataFormatterTest.php index 60681fe..211d667 100644 --- a/tests/unit/JSONDataFormatterTest.php +++ b/tests/unit/JSONDataFormatterTest.php @@ -49,8 +49,6 @@ class JSONDataFormatterTest extends SapphireTest // Test method improvements, more fields rather than just id, href, className $this->assertEquals(9, $json->ID, "ID not equal"); $this->assertEquals("SilverStripe\\RestfulServer\\Tests\\Stubs\\JSONDataFormatterTypeTestObject", $json->ClassName, "Class not equal"); - $this->assertEquals(date('Y-m-d H:i:s', time() - 1), $json->LastEdited, "Last edited does not equal"); - $this->assertEquals(date('Y-m-d H:i:s', time() - 1), $json->Created, "Created at does not equal"); $this->assertEquals("Test Object", $json->Name, "Name not equal"); $this->assertEquals(false, $json->Active, "Active not equal to false"); $this->assertEquals(0, $json->Sort, "Sort not equal to 0"); From 2c4803bd42dafd8f77097a8232ad9db8488d8c5b Mon Sep 17 00:00:00 2001 From: Antony Videtta Date: Thu, 23 Jan 2020 15:32:41 +1300 Subject: [PATCH 5/6] Missing yml config --- tests/unit/JSONDataFormatterTest.php | 2 +- tests/unit/JSONDataFormatterTest.yml | 21 ++++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/tests/unit/JSONDataFormatterTest.php b/tests/unit/JSONDataFormatterTest.php index 211d667..2016479 100644 --- a/tests/unit/JSONDataFormatterTest.php +++ b/tests/unit/JSONDataFormatterTest.php @@ -31,7 +31,7 @@ class JSONDataFormatterTest extends SapphireTest // Grab test object $formatter = new JSONDataFormatter(); - $parent = $this->objFromFixture(JSONDataFormatterTypeTestObject::class, 'parent'); + $parent = $this->objFromFixture(JSONDataFormatterTypeTestObject::class, 'original'); $json = json_decode($formatter->convertDataObject($parent)); // Returns valid array and isn't null diff --git a/tests/unit/JSONDataFormatterTest.yml b/tests/unit/JSONDataFormatterTest.yml index 38f2b2a..60ad759 100644 --- a/tests/unit/JSONDataFormatterTest.yml +++ b/tests/unit/JSONDataFormatterTest.yml @@ -2,7 +2,26 @@ SilverStripe\RestfulServer\Tests\Stubs\JSONDataFormatterTypeTestObject: foo: ID: 8 Name: Test Object 1 - parent: + original: ID: 9 Name: Test Object Children: =>SilverStripe\RestfulServer\Tests\Stubs\JSONDataFormatterTypeTestObject.foo + parent: + Name: Parent + Active: true + Sort: 17 + Average: 1.2345 + child1: + Name: Child 1 + Active: 1 + Sort: 4 + Average: 6.78 + Parent: =>SilverStripe\RestfulServer\Tests\Stubs\JSONDataFormatterTypeTestObject.parent + child2: + Name: Child 2 + Active: false + Sort: 9 + Average: 1 + Parent: =>SilverStripe\RestfulServer\Tests\Stubs\JSONDataFormatterTypeTestObject.parent + child3: + Parent: =>SilverStripe\RestfulServer\Tests\Stubs\JSONDataFormatterTypeTestObject.parent From 07ddd20399aec4bff00d9487decd18feb18f0d4e Mon Sep 17 00:00:00 2001 From: Antony Videtta Date: Fri, 24 Jan 2020 07:50:47 +1300 Subject: [PATCH 6/6] reinstate old test --- tests/unit/JSONDataFormatterTest.php | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/tests/unit/JSONDataFormatterTest.php b/tests/unit/JSONDataFormatterTest.php index 2016479..b66cad6 100644 --- a/tests/unit/JSONDataFormatterTest.php +++ b/tests/unit/JSONDataFormatterTest.php @@ -31,8 +31,29 @@ class JSONDataFormatterTest extends SapphireTest // Grab test object $formatter = new JSONDataFormatter(); - $parent = $this->objFromFixture(JSONDataFormatterTypeTestObject::class, 'original'); - $json = json_decode($formatter->convertDataObject($parent)); + + $parent = $this->objFromFixture(JSONDataFormatterTypeTestObject::class, 'parent'); + $json = $formatter->convertDataObject($parent); + + $this->assertRegexp('/"ID":\d+/', $json, 'PK casted to integer'); + $this->assertRegexp('/"Created":"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}"/', $json, 'Datetime casted to string'); + $this->assertContains('"Name":"Parent"', $json, 'String casted to string'); + $this->assertContains('"Active":true', $json, 'Boolean casted to boolean'); + $this->assertContains('"Sort":17', $json, 'Integer casted to integer'); + $this->assertContains('"Average":1.2345', $json, 'Float casted to float'); + $this->assertContains('"ParentID":0', $json, 'Empty FK is 0'); + + $child3 = $this->objFromFixture(JSONDataFormatterTypeTestObject::class, 'child3'); + $json = $formatter->convertDataObject($child3); + + $this->assertContains('"Name":null', $json, 'Empty string is null'); + $this->assertContains('"Active":false', $json, 'Empty boolean is false'); + $this->assertContains('"Sort":0', $json, 'Empty integer is 0'); + $this->assertContains('"Average":0', $json, 'Empty float is 0'); + $this->assertRegexp('/"ParentID":\d+/', $json, 'FK casted to integer'); + + $original = $this->objFromFixture(JSONDataFormatterTypeTestObject::class, 'original'); + $json = json_decode($formatter->convertDataObject($original)); // Returns valid array and isn't null $this->assertNotEmpty($json, 'Array is empty');