From 2bfcee476544ea594ef84040e72135bb0c91d8a3 Mon Sep 17 00:00:00 2001 From: johguentner Date: Sat, 9 Jul 2022 13:03:51 +0200 Subject: [PATCH 01/20] fix: check if content is set for rollup properties - specific rollup properties can be null, which lead to issues of setting the content of the property - this fix checks the existence of content and skips setting the content, if it is null (e.g. empty selects, multi_selects etc.) - fixes issue: Rollup that refers to an empty Select property throws exception during database query --- src/Entities/Properties/Rollup.php | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Entities/Properties/Rollup.php b/src/Entities/Properties/Rollup.php index d0fa577..3e23c65 100644 --- a/src/Entities/Properties/Rollup.php +++ b/src/Entities/Properties/Rollup.php @@ -37,7 +37,7 @@ protected function fillFromRaw(): void break; default: throw new HandlingException("Unexpected rollupType {$this->rollupType}"); - } + } } } @@ -89,12 +89,21 @@ private function setRollupContentArray() // TODO $rollupPropertyItem['id'] = 'undefined'; - $this->content->add( - Property::fromResponse('', $rollupPropertyItem) - ); + if ($this->isRollupPropertyContentSet($rollupPropertyItem)) { + $this->content->add( + Property::fromResponse('', $rollupPropertyItem) + ); + } } } + private function isRollupPropertyContentSet($rollupPropertyItem): bool + { + return Arr::exists($rollupPropertyItem, 'type') + && Arr::exists($rollupPropertyItem, $rollupPropertyItem['type']) + && !is_null($rollupPropertyItem[$rollupPropertyItem['type']]); + } + private function setRollupContentDate() { $this->content = new RichDate(); From 2507d0f220050a3a03d98dda64a9b3537bb0a1e3 Mon Sep 17 00:00:00 2001 From: Di Date: Sat, 9 Jul 2022 13:04:06 +0200 Subject: [PATCH 02/20] Apply fixes from StyleCI (#67) --- src/Entities/Properties/Rollup.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Entities/Properties/Rollup.php b/src/Entities/Properties/Rollup.php index 3e23c65..7135eae 100644 --- a/src/Entities/Properties/Rollup.php +++ b/src/Entities/Properties/Rollup.php @@ -101,7 +101,7 @@ private function isRollupPropertyContentSet($rollupPropertyItem): bool { return Arr::exists($rollupPropertyItem, 'type') && Arr::exists($rollupPropertyItem, $rollupPropertyItem['type']) - && !is_null($rollupPropertyItem[$rollupPropertyItem['type']]); + && ! is_null($rollupPropertyItem[$rollupPropertyItem['type']]); } private function setRollupContentDate() From 7a9da2d9cbbdd72184333181e4580f5ef41dc08a Mon Sep 17 00:00:00 2001 From: johguentner Date: Sat, 9 Jul 2022 15:41:37 +0200 Subject: [PATCH 03/20] polish/fix: control over 'includeTime' of Date - introduce new methods for actively including time ('include Time') of a Date Properties within a Notion Page - existing init of value for a Date Property will force a date without time - ::valueWithTime(...) in Date Property and ->setDateTime(...) in Page will force the inclusion of time, if pushed to Notion - ! breaking: result of ::value(...) of Date Property and ->setDate(...) of Page are changing (time will not be included) --- src/Entities/Page.php | 13 ++++++++++ src/Entities/Properties/Date.php | 33 +++++++++++++++++++++++++ src/Entities/PropertyItems/RichDate.php | 17 ++++++++++--- 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/src/Entities/Page.php b/src/Entities/Page.php index 7220671..77e6609 100644 --- a/src/Entities/Page.php +++ b/src/Entities/Page.php @@ -344,6 +344,19 @@ public function setDate(string $propertyTitle, DateTime $start, ?DateTime $end = return $this; } + /** + * @param $propertyTitle + * @param $start + * @param $end + * @return Page + */ + public function setDateTime(string $propertyTitle, DateTime $start, ?DateTime $end = null): Page + { + $this->set($propertyTitle, Date::valueWithTime($start, $end)); + + return $this; + } + /** * @param $propertyTitle * @param $relationIds diff --git a/src/Entities/Properties/Date.php b/src/Entities/Properties/Date.php index af46e64..7cfc634 100644 --- a/src/Entities/Properties/Date.php +++ b/src/Entities/Properties/Date.php @@ -26,6 +26,39 @@ public static function value(?DateTime $start, ?DateTime $end = null): Date $dateProperty = new Date(); $dateProperty->content = $richDate; + if ($richDate->isRange()) { + $dateProperty->rawContent = [ + 'date' => [ + 'start' => $start->format('Y-m-d'), + 'end' => $end->format('Y-m-d'), + ], + ]; + } else { + $dateProperty->rawContent = [ + 'date' => [ + 'start' => $start->format('Y-m-d'), + ], + ]; + } + + return $dateProperty; + } + + /** + * @param $start + * @param $end + * @return Date + */ + public static function valueWithTime(?DateTime $start, ?DateTime $end = null): Date + { + $richDate = new RichDate(); + $richDate->setStart($start); + $richDate->setEnd($end); + $richDate->setHasTime(true); + + $dateProperty = new Date(); + $dateProperty->content = $richDate; + if ($richDate->isRange()) { $dateProperty->rawContent = [ 'date' => [ diff --git a/src/Entities/PropertyItems/RichDate.php b/src/Entities/PropertyItems/RichDate.php index 81a3d18..8464938 100644 --- a/src/Entities/PropertyItems/RichDate.php +++ b/src/Entities/PropertyItems/RichDate.php @@ -11,11 +11,9 @@ */ class RichDate extends Entity { - /** - * @var string - */ protected DateTime $start; protected ?DateTime $end = null; + protected bool $hasTime = false; /** * @param array $responseData @@ -70,6 +68,14 @@ public function getEnd(): ?DateTime return $this->end; } + /** + * @return bool + */ + public function getHasTime(): bool + { + return $this->hasTime; + } + public function setStart($start): void { $this->start = $start; @@ -79,4 +85,9 @@ public function setEnd($end): void { $this->end = $end; } + + public function setHasTime($hasTime): void + { + $this->hasTime = $hasTime; + } } From 811172023328b6d15b664f9f1c2f71d8bba46024 Mon Sep 17 00:00:00 2001 From: johguentner Date: Sat, 9 Jul 2022 16:21:13 +0200 Subject: [PATCH 04/20] add: test for edge case of empty Select in Rollup - testing the edge case of the fix "65-rollup-with-empty-select-fix" --- tests/EndpointDatabaseTest.php | 28 ++++ ...esponse_query_rollup_empty_select_200.json | 148 ++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 tests/stubs/endpoints/databases/response_query_rollup_empty_select_200.json diff --git a/tests/EndpointDatabaseTest.php b/tests/EndpointDatabaseTest.php index 5efe4da..42ce3a9 100644 --- a/tests/EndpointDatabaseTest.php +++ b/tests/EndpointDatabaseTest.php @@ -157,4 +157,32 @@ public function it_throws_a_notion_exception_bad_request() Notion::database('8284f3ff77e24d4a939d19459e4d6bdc')->query(); } + + ## EDGE CASES + + /** @test */ + public function it_queries_a_database_with_a_rollup_property_with_empty_selects() + { + // success /v1/databases/DATABASE_DOES_EXIST/query + Http::fake([ + 'https://api.notion.com/v1/databases/11971214ce574df7a58389c1deda61d7/query*' => Http::response( + json_decode(file_get_contents("tests/stubs/endpoints/databases/response_query_rollup_empty_select_200.json"), true), + 200, + ['Headers'] + ), + ]); + + $result = Notion::database('11971214ce574df7a58389c1deda61d7')->query(); + + $this->assertInstanceOf(PageCollection::class, $result); + + $resultCollection = $result->asCollection(); + + $this->assertIsIterable($resultCollection); + $this->assertContainsOnly(Page::class, $resultCollection); + + // check page object + $page = $resultCollection->first(); + $this->assertEquals(0, $page->getProperty('Rollup')->getContent()->count()); + } } diff --git a/tests/stubs/endpoints/databases/response_query_rollup_empty_select_200.json b/tests/stubs/endpoints/databases/response_query_rollup_empty_select_200.json new file mode 100644 index 0000000..d0a46e5 --- /dev/null +++ b/tests/stubs/endpoints/databases/response_query_rollup_empty_select_200.json @@ -0,0 +1,148 @@ +{ + "object": "list", + "results": [ + { + "object": "page", + "id": "1321e6b6-0771-48bb-9814-6501c2ec3c32", + "created_time": "2022-07-09T10:29:00.000Z", + "last_edited_time": "2022-07-09T10:45:00.000Z", + "created_by": { + "object": "user", + "id": "04536682-601a-4531-a18f-4fa89fdf14a8" + }, + "last_edited_by": { + "object": "user", + "id": "04531682-603a-4531-a18f-1fa89fdfb4a8" + }, + "cover": null, + "icon": null, + "parent": { + "type": "database_id", + "database_id": "15971224-ce57-4df1-a583-89c1deca63d7" + }, + "archived": false, + "properties": { + "Test Rollup Problem": { + "id": "G|zT", + "type": "relation", + "relation": [ + { + "id": "dcad104b-222c-4d63-b83e-852f22612f4a" + }, + { + "id": "3611ce31-ae52-45dc-bc5a-10626ca19ab5" + } + ] + }, + "Rollup": { + "id": "JCh`", + "type": "rollup", + "rollup": { + "type": "array", + "array": [ + { + "type": "select", + "select": null + } + ], + "function": "show_original" + } + }, + "Name": { + "id": "title", + "type": "title", + "title": [] + } + }, + "url": "https:\/\/www.notion.so\/132de616077648bb98146501c2ec3c32" + }, + { + "object": "page", + "id": "43dd6b90-6bde-48ea-9f06-79fee96de99c", + "created_time": "2022-07-09T10:29:00.000Z", + "last_edited_time": "2022-07-09T10:29:00.000Z", + "created_by": { + "object": "user", + "id": "04d36682-603a-4531-a18f-4fa19fdfb4a8" + }, + "last_edited_by": { + "object": "user", + "id": "04d36682-603a-4531-a18f-4fa891dfb4a8" + }, + "cover": null, + "icon": null, + "parent": { + "type": "database_id", + "database_id": "1d9712d4-ce57-4df7-a583-89c1dedac3d7" + }, + "archived": false, + "properties": { + "Test Rollup Problem": { + "id": "G|zT", + "type": "relation", + "relation": [] + }, + "Rollup": { + "id": "JCh`", + "type": "rollup", + "rollup": { + "type": "array", + "array": [], + "function": "show_original" + } + }, + "Name": { + "id": "title", + "type": "title", + "title": [] + } + }, + "url": "https:\/\/www.notion.so\/430d6b9d6b9e48ea9f067cfee96de9d9" + }, + { + "object": "page", + "id": "788c67fe-84d2-4cf8-aab6-6cd6448d98b2", + "created_time": "2022-07-09T10:29:00.000Z", + "last_edited_time": "2022-07-09T10:29:00.000Z", + "created_by": { + "object": "user", + "id": "04536682-613a-4531-a18f-4fd89fdfb4a8" + }, + "last_edited_by": { + "object": "user", + "id": "04436622-603a-4531-a18f-4fa89fdfb4a8" + }, + "cover": null, + "icon": null, + "parent": { + "type": "database_id", + "database_id": "15972224-ce57-4df7-a583-89c1de1a63dd" + }, + "archived": false, + "properties": { + "Test Rollup Problem": { + "id": "G|zT", + "type": "relation", + "relation": [] + }, + "Rollup": { + "id": "JCh`", + "type": "rollup", + "rollup": { + "type": "array", + "array": [], + "function": "show_original" + } + }, + "Name": { + "id": "title", + "type": "title", + "title": [] + } + }, + "url": "https:\/\/www.notion.so\/788c67de84d24cf8dab660d64c8998b2" + } + ], + "next_cursor": null, + "has_more": false +} \ No newline at end of file From 32fbfc6f8b822bfa0c9ff579c1d9c22ab5a2abd9 Mon Sep 17 00:00:00 2001 From: Di Date: Sat, 9 Jul 2022 16:21:28 +0200 Subject: [PATCH 05/20] Apply fixes from StyleCI (#70) --- tests/EndpointDatabaseTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/EndpointDatabaseTest.php b/tests/EndpointDatabaseTest.php index 42ce3a9..ba07609 100644 --- a/tests/EndpointDatabaseTest.php +++ b/tests/EndpointDatabaseTest.php @@ -158,7 +158,7 @@ public function it_throws_a_notion_exception_bad_request() Notion::database('8284f3ff77e24d4a939d19459e4d6bdc')->query(); } - ## EDGE CASES + //# EDGE CASES /** @test */ public function it_queries_a_database_with_a_rollup_property_with_empty_selects() @@ -166,7 +166,7 @@ public function it_queries_a_database_with_a_rollup_property_with_empty_selects( // success /v1/databases/DATABASE_DOES_EXIST/query Http::fake([ 'https://api.notion.com/v1/databases/11971214ce574df7a58389c1deda61d7/query*' => Http::response( - json_decode(file_get_contents("tests/stubs/endpoints/databases/response_query_rollup_empty_select_200.json"), true), + json_decode(file_get_contents('tests/stubs/endpoints/databases/response_query_rollup_empty_select_200.json'), true), 200, ['Headers'] ), From 0283febdf6d85f396d255325bf0f5b9e2fdc9141 Mon Sep 17 00:00:00 2001 From: johguentner Date: Sat, 9 Jul 2022 16:38:47 +0200 Subject: [PATCH 06/20] fix test: breaking-changes in Date prop handling - change date formatting within EndointPagesTest (it_assembles_properties_for_a_new_page) - making sure to use 'Y-m-d' format instead of 'c', to match new Date Property handling --- tests/EndpointPagesTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/EndpointPagesTest.php b/tests/EndpointPagesTest.php index 20f641c..2c5d869 100644 --- a/tests/EndpointPagesTest.php +++ b/tests/EndpointPagesTest.php @@ -182,9 +182,9 @@ public function it_assembles_properties_for_a_new_page() $this->assertArrayHasKey('date', $dateRangeContent); $this->assertCount(2, $dateRangeContent['date']); $this->assertArrayHasKey('start', $dateRangeContent['date']); - $this->assertEquals($dateRangeStartValue->format('c'), $dateRangeContent['date']['start']); + $this->assertEquals($dateRangeStartValue->format('Y-m-d'), $dateRangeContent['date']['start']); $this->assertArrayHasKey('end', $dateRangeContent['date']); - $this->assertEquals($dateRangeEndValue->format('c'), $dateRangeContent['date']['end']); + $this->assertEquals($dateRangeEndValue->format('Y-m-d'), $dateRangeContent['date']['end']); // date $dateProp = $page->getProperty($dateKey); @@ -195,7 +195,7 @@ public function it_assembles_properties_for_a_new_page() $this->assertArrayHasKey('date', $dateContent); $this->assertCount(1, $dateContent['date']); $this->assertArrayHasKey('start', $dateContent['date']); - $this->assertEquals($dateValue->format('c'), $dateContent['date']['start']); + $this->assertEquals($dateValue->format('Y-m-d'), $dateContent['date']['start']); // email $this->assertTrue($this->assertContainsInstanceOf(Email::class, $properties)); From 420c0371d7a722f09c5c891435e52621800e01bc Mon Sep 17 00:00:00 2001 From: johguentner Date: Sat, 9 Jul 2022 16:45:13 +0200 Subject: [PATCH 07/20] add tests: for Date Property with forced time --- tests/EndpointPagesTest.php | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/EndpointPagesTest.php b/tests/EndpointPagesTest.php index 2c5d869..726f3be 100644 --- a/tests/EndpointPagesTest.php +++ b/tests/EndpointPagesTest.php @@ -106,9 +106,11 @@ public function it_assembles_properties_for_a_new_page() $checkboxKey = 'CheckboxProperty'; $checkboxValue = true; $dateRangeKey = 'DateRangeProperty'; + $dateTimeRangeKey = 'DateTimeRangeProperty'; $dateRangeStartValue = Carbon::now()->toDateTime(); $dateRangeEndValue = Carbon::tomorrow()->toDateTime(); $dateKey = 'DateProperty'; + $dateTimeKey = 'DateTimeProperty'; $dateValue = Carbon::yesterday()->toDateTime(); $emailKey = 'EmailProperty'; $emailValue = 'notion-is-awesome@example.org'; @@ -135,7 +137,9 @@ public function it_assembles_properties_for_a_new_page() $page->setTitle('Name', $pageTitle); $page->setCheckbox($checkboxKey, $checkboxValue); $page->setDate($dateRangeKey, $dateRangeStartValue, $dateRangeEndValue); + $page->setDateTime($dateTimeRangeKey, $dateRangeStartValue, $dateRangeEndValue); $page->setDate($dateKey, $dateValue); + $page->setDateTime($dateTimeKey, $dateValue); $page->setEmail($emailKey, $emailValue); $page->setMultiSelect($multiSelectKey, $multiSelectValues); $page->setNumber($numberKey, $numberValue); @@ -186,6 +190,28 @@ public function it_assembles_properties_for_a_new_page() $this->assertArrayHasKey('end', $dateRangeContent['date']); $this->assertEquals($dateRangeEndValue->format('Y-m-d'), $dateRangeContent['date']['end']); + + // date range (with time) + $this->assertTrue( + $this->assertContainsInstanceOf(Date::class, $properties) + ); + $dateTimeRangeProp = $page->getProperty($dateTimeRangeKey); + $this->assertInstanceOf(RichDate::class, $dateTimeRangeProp->getContent()); + $dateTimeRangeContent = $dateTimeRangeProp->getContent(); + $this->assertTrue($dateTimeRangeProp->isRange()); + $this->assertEquals($dateRangeStartValue, $dateTimeRangeProp->getStart()); + $this->assertEquals($dateRangeEndValue, $dateTimeRangeProp->getEnd()); + $this->assertJson($dateTimeRangeProp->asText()); + $this->assertStringContainsString($dateRangeStartValue->format('Y-m-d H:i:s'), $dateTimeRangeProp->asText()); + $this->assertStringContainsString($dateRangeEndValue->format('Y-m-d H:i:s'), $dateTimeRangeProp->asText()); + $dateTimeRangeContent = $dateTimeRangeProp->getRawContent(); + $this->assertArrayHasKey('date', $dateTimeRangeContent); + $this->assertCount(2, $dateTimeRangeContent['date']); + $this->assertArrayHasKey('start', $dateTimeRangeContent['date']); + $this->assertEquals($dateRangeStartValue->format('c'), $dateTimeRangeContent['date']['start']); + $this->assertArrayHasKey('end', $dateTimeRangeContent['date']); + $this->assertEquals($dateRangeEndValue->format('c'), $dateTimeRangeContent['date']['end']); + // date $dateProp = $page->getProperty($dateKey); $this->assertInstanceOf(RichDate::class, $dateProp->getContent()); @@ -197,6 +223,17 @@ public function it_assembles_properties_for_a_new_page() $this->assertArrayHasKey('start', $dateContent['date']); $this->assertEquals($dateValue->format('Y-m-d'), $dateContent['date']['start']); + // date (with time) + $dateTimeProp = $page->getProperty($dateTimeKey); + $this->assertInstanceOf(RichDate::class, $dateTimeProp->getContent()); + $this->assertFalse($dateTimeProp->isRange()); + $this->assertEquals($dateValue, $dateTimeProp->getStart()); + $dateTimeContent = $dateTimeProp->getRawContent(); + $this->assertArrayHasKey('date', $dateTimeContent); + $this->assertCount(1, $dateTimeContent['date']); + $this->assertArrayHasKey('start', $dateTimeContent['date']); + $this->assertEquals($dateValue->format('c'), $dateTimeContent['date']['start']); + // email $this->assertTrue($this->assertContainsInstanceOf(Email::class, $properties)); $mailProp = $page->getProperty($emailKey); From 85a8ff8962ba88db1a9dc340a8c3966ce273d684 Mon Sep 17 00:00:00 2001 From: Di Date: Sat, 9 Jul 2022 16:45:31 +0200 Subject: [PATCH 08/20] Apply fixes from StyleCI (#71) --- tests/EndpointPagesTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/EndpointPagesTest.php b/tests/EndpointPagesTest.php index 726f3be..376caa3 100644 --- a/tests/EndpointPagesTest.php +++ b/tests/EndpointPagesTest.php @@ -190,7 +190,6 @@ public function it_assembles_properties_for_a_new_page() $this->assertArrayHasKey('end', $dateRangeContent['date']); $this->assertEquals($dateRangeEndValue->format('Y-m-d'), $dateRangeContent['date']['end']); - // date range (with time) $this->assertTrue( $this->assertContainsInstanceOf(Date::class, $properties) From 0fdd0c09906e854b58192157e6dc7539f5e124e7 Mon Sep 17 00:00:00 2001 From: johguentner Date: Sun, 10 Jul 2022 16:32:06 +0200 Subject: [PATCH 09/20] add: better pagination handling - fix problem with next_cursor handling for database queries - added raw extraction (->fillResult()) for 'has_more' and 'next_cursor' in EntityCollection (e.g. executed on querying databases) - added ->offsetByResponse(...) for handling the offset based on the previous query result (query result = EntityCollection) - added public functions for easy access to 'has_more' (->hasMoreEntries()) and 'next_cursor' (->nextCursor()) in EntityCollection --- src/Endpoints/Database.php | 14 +++++- src/Entities/Collections/EntityCollection.php | 46 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/Endpoints/Database.php b/src/Endpoints/Database.php index 03342df..0ce2959 100644 --- a/src/Endpoints/Database.php +++ b/src/Endpoints/Database.php @@ -2,6 +2,7 @@ namespace FiveamCode\LaravelNotionApi\Endpoints; +use FiveamCode\LaravelNotionApi\Entities\Collections\EntityCollection; use FiveamCode\LaravelNotionApi\Entities\Collections\PageCollection; use FiveamCode\LaravelNotionApi\Notion; use FiveamCode\LaravelNotionApi\Query\Filters\Filter; @@ -66,7 +67,7 @@ public function query(): PageCollection } // TODO Compound filters! if ($this->startCursor !== null) { - $postData['start_cursor'] = $this->startCursor; + $postData['start_cursor'] = $this->startCursor->__toString(); } if ($this->pageSize !== null) { @@ -104,4 +105,15 @@ public function sortBy(Collection $sorts): Database return $this; } + + /** + * @param EntityCollection $entityCollection + * @return $this + */ + public function offsetByResponse(EntityCollection $entityCollection): Database + { + $this->offset($entityCollection->nextCursor()); + + return $this; + } } diff --git a/src/Entities/Collections/EntityCollection.php b/src/Entities/Collections/EntityCollection.php index 2eb9435..5253b07 100644 --- a/src/Entities/Collections/EntityCollection.php +++ b/src/Entities/Collections/EntityCollection.php @@ -7,6 +7,7 @@ use FiveamCode\LaravelNotionApi\Entities\Page; use FiveamCode\LaravelNotionApi\Exceptions\HandlingException; use FiveamCode\LaravelNotionApi\Exceptions\NotionException; +use FiveamCode\LaravelNotionApi\Query\StartCursor; use Illuminate\Support\Arr; use Illuminate\Support\Collection; @@ -25,6 +26,16 @@ class EntityCollection */ protected array $rawResults = []; + /** + * @var bool + */ + protected bool $hasMore = false; + + /** + * @var string + */ + protected ?string $nextCursor = null; + /** * @var Collection */ @@ -96,6 +107,7 @@ protected function collectChildren(): void protected function fillFromRaw() { $this->fillResult(); + $this->fillCursorInformation(); } protected function fillResult() @@ -103,6 +115,16 @@ protected function fillResult() $this->rawResults = $this->responseData['results']; } + protected function fillCursorInformation() + { + if (Arr::exists($this->responseData, 'has_more')) { + $this->hasMore = $this->responseData['has_more']; + } + if (Arr::exists($this->responseData, 'next_cursor')) { + $this->nextCursor = $this->responseData['next_cursor']; + } + } + /** * @return array */ @@ -111,6 +133,14 @@ public function getRawResponse(): array return $this->responseData; } + /** + * @return string + */ + public function getRawNextCursor(): ?string + { + return $this->nextCursor; + } + /** * @return Collection */ @@ -128,4 +158,20 @@ public function asJson(): string return $item->toArray(); }); } + + /** + * @return bool + */ + public function hasMoreEntries(): bool + { + return $this->hasMore; + } + + /** + * @return StartCursor + */ + public function nextCursor(): StartCursor + { + return new StartCursor($this->getRawNextCursor()); + } } From 5d1181792c7475101210183738e89d88f2c298d1 Mon Sep 17 00:00:00 2001 From: Di Date: Sun, 10 Jul 2022 16:32:25 +0200 Subject: [PATCH 10/20] Apply fixes from StyleCI (#72) --- src/Endpoints/Database.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Endpoints/Database.php b/src/Endpoints/Database.php index 0ce2959..90a1c90 100644 --- a/src/Endpoints/Database.php +++ b/src/Endpoints/Database.php @@ -107,7 +107,7 @@ public function sortBy(Collection $sorts): Database } /** - * @param EntityCollection $entityCollection + * @param EntityCollection $entityCollection * @return $this */ public function offsetByResponse(EntityCollection $entityCollection): Database From dafd6de8164b90f888fe7939da83173d987fd88e Mon Sep 17 00:00:00 2001 From: johguentner Date: Sun, 10 Jul 2022 18:04:50 +0200 Subject: [PATCH 11/20] polish: handling for empty next_cursor - and add return type to StartCursor '__toString' --- src/Entities/Collections/EntityCollection.php | 6 ++++-- src/Query/StartCursor.php | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Entities/Collections/EntityCollection.php b/src/Entities/Collections/EntityCollection.php index 5253b07..52ad397 100644 --- a/src/Entities/Collections/EntityCollection.php +++ b/src/Entities/Collections/EntityCollection.php @@ -170,8 +170,10 @@ public function hasMoreEntries(): bool /** * @return StartCursor */ - public function nextCursor(): StartCursor + public function nextCursor(): ?StartCursor { - return new StartCursor($this->getRawNextCursor()); + $rawNextCursor = $this->getRawNextCursor(); + if($rawNextCursor === null) return null; + return new StartCursor($rawNextCursor); } } diff --git a/src/Query/StartCursor.php b/src/Query/StartCursor.php index 02d20e6..4b044ff 100644 --- a/src/Query/StartCursor.php +++ b/src/Query/StartCursor.php @@ -22,7 +22,7 @@ public function __construct(string $cursor) $this->cursor = $cursor; } - public function __toString() + public function __toString(): string { return $this->cursor; } From e1b3acacdcb0c5bd0d16eda6c8b635a73a6ac5a1 Mon Sep 17 00:00:00 2001 From: johguentner Date: Sun, 10 Jul 2022 18:09:24 +0200 Subject: [PATCH 12/20] add tests for improved pagination handling - test without offset in query and with next_cursor in response - test with offset in query (from previous response) and without next_cursor in response --- tests/EndpointDatabaseTest.php | 59 +++++++ .../response_query_offset_end_200.json | 163 ++++++++++++++++++ .../response_query_offset_start_200.json | 163 ++++++++++++++++++ 3 files changed, 385 insertions(+) create mode 100644 tests/stubs/endpoints/databases/response_query_offset_end_200.json create mode 100644 tests/stubs/endpoints/databases/response_query_offset_start_200.json diff --git a/tests/EndpointDatabaseTest.php b/tests/EndpointDatabaseTest.php index 5efe4da..d08060a 100644 --- a/tests/EndpointDatabaseTest.php +++ b/tests/EndpointDatabaseTest.php @@ -8,6 +8,7 @@ use FiveamCode\LaravelNotionApi\Exceptions\NotionException; use FiveamCode\LaravelNotionApi\Query\Filters\Filter; use FiveamCode\LaravelNotionApi\Query\Sorting; +use FiveamCode\LaravelNotionApi\Query\StartCursor; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Http; use Notion; @@ -157,4 +158,62 @@ public function it_throws_a_notion_exception_bad_request() Notion::database('8284f3ff77e24d4a939d19459e4d6bdc')->query(); } + + /** @test */ + public function it_queries_a_database_with_and_without_offset_and_processes_result() + { + // success /v1/databases/DATABASE_DOES_EXIST/query + Http::fake([ + 'https://api.notion.com/v1/databases/8284f3ff77e24d4a939d19459e4d6bdc/query*' => + Http::sequence() + ->push( + json_decode(file_get_contents("tests/stubs/endpoints/databases/response_query_offset_start_200.json"), true), + 200, + ['Headers'] + ) + ->push( + json_decode(file_get_contents("tests/stubs/endpoints/databases/response_query_offset_end_200.json"), true), + 200, + ['Headers'] + ) + ]); + + $result = Notion::database('8284f3ff77e24d4a939d19459e4d6bdc') + ->query(); + + //check instance and offset + $this->assertInstanceOf(PageCollection::class, $result); + $this->assertEquals(true, $result->hasMoreEntries()); + $this->assertInstanceOf(StartCursor::class, $result->nextCursor()); + $this->assertEquals('1500b7c7-329f-4854-8912-4c6972a8743e', $result->nextCursor()); + $this->assertEquals('1500b7c7-329f-4854-8912-4c6972a8743e', $result->getRawNextCursor()); + + $resultCollection = $result->asCollection(); + + $this->assertIsIterable($resultCollection); + $this->assertContainsOnly(Page::class, $resultCollection); + + // check page object + $page = $resultCollection->first(); + $this->assertEquals('Betty Holberton', $page->getTitle()); + + $resultWithOffset = Notion::database('8284f3ff77e24d4a939d19459e4d6bdc') + ->offsetByResponse($result) + ->query(); + + // check instance and offset + $this->assertInstanceOf(PageCollection::class, $resultWithOffset); + $this->assertEquals(false, $resultWithOffset->hasMoreEntries()); + $this->assertEquals(null, $resultWithOffset->nextCursor()); + $this->assertEquals(null, $resultWithOffset->getRawNextCursor()); + + $resultWithOffsetCollection = $resultWithOffset->asCollection(); + + $this->assertIsIterable($resultWithOffsetCollection); + $this->assertContainsOnly(Page::class, $resultWithOffsetCollection); + + // check page object + $page = $resultWithOffsetCollection->first(); + $this->assertEquals('Betty Holberton', $page->getTitle()); + } } diff --git a/tests/stubs/endpoints/databases/response_query_offset_end_200.json b/tests/stubs/endpoints/databases/response_query_offset_end_200.json new file mode 100644 index 0000000..19b716c --- /dev/null +++ b/tests/stubs/endpoints/databases/response_query_offset_end_200.json @@ -0,0 +1,163 @@ +{ + "object": "list", + "results": [ + { + "object": "page", + "id": "1500b7c7-329f-4854-8912-4c6972a8743e", + "created_time": "2021-05-24T11:03:00.000Z", + "last_edited_time": "2021-05-24T11:13:00.000Z", + "parent": { + "type": "database_id", + "database_id": "8284f3ff-77e2-4d4a-939d-19459e4d6bdc" + }, + "archived": false, + "properties": { + "Birth year": { + "id": "_f<<", + "type": "rich_text", + "rich_text": [ + { + "type": "text", + "text": { + "content": "1917", + "link": null + }, + "annotations": { + "bold": false, + "italic": false, + "strikethrough": false, + "underline": false, + "code": false, + "color": "default" + }, + "plain_text": "1917", + "href": null + } + ] + }, + "Known for": { + "id": "aUmo", + "type": "multi_select", + "multi_select": [ + { + "id": "f55ee1a3-e67f-4793-ba3f-5dac02938a5f", + "name": "ENIAC", + "color": "purple" + }, + { + "id": "2016de6e-5325-4549-8e1a-60ee7570382a", + "name": "UNIVAC", + "color": "default" + }, + { + "id": "55c46053-f87e-40e9-8070-6c398939fed6", + "name": "Breakpoints", + "color": "red" + } + ] + }, + "Name": { + "id": "title", + "type": "title", + "title": [ + { + "type": "text", + "text": { + "content": "Betty Holberton", + "link": null + }, + "annotations": { + "bold": false, + "italic": false, + "strikethrough": false, + "underline": false, + "code": false, + "color": "default" + }, + "plain_text": "Betty Holberton", + "href": null + } + ] + } + } + }, + { + "object": "page", + "id": "ab2a7a85-08a1-4dfc-be89-0e30aeffc0f6", + "created_time": "2021-05-24T11:03:02.464Z", + "last_edited_time": "2021-05-24T11:12:00.000Z", + "parent": { + "type": "database_id", + "database_id": "8284f3ff-77e2-4d4a-939d-19459e4d6bdc" + }, + "archived": false, + "properties": { + "Birth year": { + "id": "_f<<", + "type": "rich_text", + "rich_text": [ + { + "type": "text", + "text": { + "content": "1906", + "link": null + }, + "annotations": { + "bold": false, + "italic": false, + "strikethrough": false, + "underline": false, + "code": false, + "color": "default" + }, + "plain_text": "1906", + "href": null + } + ] + }, + "Known for": { + "id": "aUmo", + "type": "multi_select", + "multi_select": [ + { + "id": "1ada9715-0139-4c1c-b1af-9d8d2fe80ea2", + "name": "COBOL", + "color": "orange" + }, + { + "id": "2016de6e-5325-4549-8e1a-60ee7570382a", + "name": "UNIVAC", + "color": "default" + } + ] + }, + "Name": { + "id": "title", + "type": "title", + "title": [ + { + "type": "text", + "text": { + "content": "Grace Hopper", + "link": null + }, + "annotations": { + "bold": false, + "italic": false, + "strikethrough": false, + "underline": false, + "code": false, + "color": "default" + }, + "plain_text": "Grace Hopper", + "href": null + } + ] + } + } + } + ], + "next_cursor": null, + "has_more": false + } + \ No newline at end of file diff --git a/tests/stubs/endpoints/databases/response_query_offset_start_200.json b/tests/stubs/endpoints/databases/response_query_offset_start_200.json new file mode 100644 index 0000000..ddca2de --- /dev/null +++ b/tests/stubs/endpoints/databases/response_query_offset_start_200.json @@ -0,0 +1,163 @@ +{ + "object": "list", + "results": [ + { + "object": "page", + "id": "1500b7c7-329f-4854-8912-4c6972a8743e", + "created_time": "2021-05-24T11:03:00.000Z", + "last_edited_time": "2021-05-24T11:13:00.000Z", + "parent": { + "type": "database_id", + "database_id": "8284f3ff-77e2-4d4a-939d-19459e4d6bdc" + }, + "archived": false, + "properties": { + "Birth year": { + "id": "_f<<", + "type": "rich_text", + "rich_text": [ + { + "type": "text", + "text": { + "content": "1917", + "link": null + }, + "annotations": { + "bold": false, + "italic": false, + "strikethrough": false, + "underline": false, + "code": false, + "color": "default" + }, + "plain_text": "1917", + "href": null + } + ] + }, + "Known for": { + "id": "aUmo", + "type": "multi_select", + "multi_select": [ + { + "id": "f55ee1a3-e67f-4793-ba3f-5dac02938a5f", + "name": "ENIAC", + "color": "purple" + }, + { + "id": "2016de6e-5325-4549-8e1a-60ee7570382a", + "name": "UNIVAC", + "color": "default" + }, + { + "id": "55c46053-f87e-40e9-8070-6c398939fed6", + "name": "Breakpoints", + "color": "red" + } + ] + }, + "Name": { + "id": "title", + "type": "title", + "title": [ + { + "type": "text", + "text": { + "content": "Betty Holberton", + "link": null + }, + "annotations": { + "bold": false, + "italic": false, + "strikethrough": false, + "underline": false, + "code": false, + "color": "default" + }, + "plain_text": "Betty Holberton", + "href": null + } + ] + } + } + }, + { + "object": "page", + "id": "ab2a7a85-08a1-4dfc-be89-0e30aeffc0f6", + "created_time": "2021-05-24T11:03:02.464Z", + "last_edited_time": "2021-05-24T11:12:00.000Z", + "parent": { + "type": "database_id", + "database_id": "8284f3ff-77e2-4d4a-939d-19459e4d6bdc" + }, + "archived": false, + "properties": { + "Birth year": { + "id": "_f<<", + "type": "rich_text", + "rich_text": [ + { + "type": "text", + "text": { + "content": "1906", + "link": null + }, + "annotations": { + "bold": false, + "italic": false, + "strikethrough": false, + "underline": false, + "code": false, + "color": "default" + }, + "plain_text": "1906", + "href": null + } + ] + }, + "Known for": { + "id": "aUmo", + "type": "multi_select", + "multi_select": [ + { + "id": "1ada9715-0139-4c1c-b1af-9d8d2fe80ea2", + "name": "COBOL", + "color": "orange" + }, + { + "id": "2016de6e-5325-4549-8e1a-60ee7570382a", + "name": "UNIVAC", + "color": "default" + } + ] + }, + "Name": { + "id": "title", + "type": "title", + "title": [ + { + "type": "text", + "text": { + "content": "Grace Hopper", + "link": null + }, + "annotations": { + "bold": false, + "italic": false, + "strikethrough": false, + "underline": false, + "code": false, + "color": "default" + }, + "plain_text": "Grace Hopper", + "href": null + } + ] + } + } + } + ], + "next_cursor": "1500b7c7-329f-4854-8912-4c6972a8743e", + "has_more": true + } + \ No newline at end of file From bc8a3e6cc717a7e2c23319f89e361d773628b90e Mon Sep 17 00:00:00 2001 From: Di Date: Sun, 10 Jul 2022 18:09:38 +0200 Subject: [PATCH 13/20] Apply fixes from StyleCI (#74) --- src/Entities/Collections/EntityCollection.php | 5 ++++- tests/EndpointDatabaseTest.php | 9 ++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Entities/Collections/EntityCollection.php b/src/Entities/Collections/EntityCollection.php index 52ad397..6106f6f 100644 --- a/src/Entities/Collections/EntityCollection.php +++ b/src/Entities/Collections/EntityCollection.php @@ -173,7 +173,10 @@ public function hasMoreEntries(): bool public function nextCursor(): ?StartCursor { $rawNextCursor = $this->getRawNextCursor(); - if($rawNextCursor === null) return null; + if ($rawNextCursor === null) { + return null; + } + return new StartCursor($rawNextCursor); } } diff --git a/tests/EndpointDatabaseTest.php b/tests/EndpointDatabaseTest.php index d08060a..d3f69ee 100644 --- a/tests/EndpointDatabaseTest.php +++ b/tests/EndpointDatabaseTest.php @@ -164,18 +164,17 @@ public function it_queries_a_database_with_and_without_offset_and_processes_resu { // success /v1/databases/DATABASE_DOES_EXIST/query Http::fake([ - 'https://api.notion.com/v1/databases/8284f3ff77e24d4a939d19459e4d6bdc/query*' => - Http::sequence() + 'https://api.notion.com/v1/databases/8284f3ff77e24d4a939d19459e4d6bdc/query*' => Http::sequence() ->push( - json_decode(file_get_contents("tests/stubs/endpoints/databases/response_query_offset_start_200.json"), true), + json_decode(file_get_contents('tests/stubs/endpoints/databases/response_query_offset_start_200.json'), true), 200, ['Headers'] ) ->push( - json_decode(file_get_contents("tests/stubs/endpoints/databases/response_query_offset_end_200.json"), true), + json_decode(file_get_contents('tests/stubs/endpoints/databases/response_query_offset_end_200.json'), true), 200, ['Headers'] - ) + ), ]); $result = Notion::database('8284f3ff77e24d4a939d19459e4d6bdc') From c541080e465edc06057df5a402d44a5a3eecbab2 Mon Sep 17 00:00:00 2001 From: johguentner Date: Mon, 11 Jul 2022 10:18:41 +0200 Subject: [PATCH 14/20] polish/fix: read (fetch) hasTime in date property - refactor 'isset' to Arr::exists - evaluate if time is included, based on 'T' in datetime-string (when reading a date property from Notion) - allow return of null within ->getEnd() getter (allow same in RichDate::class) - add ->hasTime() getter, which calls ->hasTime() from RichDate::class (content) - refactor ->getHasTime() from RichDate::class to ->hasTime() --- src/Entities/Properties/Date.php | 24 ++++++++++++++++++++---- src/Entities/PropertyItems/RichDate.php | 4 ++-- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/Entities/Properties/Date.php b/src/Entities/Properties/Date.php index 7cfc634..8465ded 100644 --- a/src/Entities/Properties/Date.php +++ b/src/Entities/Properties/Date.php @@ -6,6 +6,7 @@ use FiveamCode\LaravelNotionApi\Entities\Contracts\Modifiable; use FiveamCode\LaravelNotionApi\Entities\PropertyItems\RichDate; use FiveamCode\LaravelNotionApi\Exceptions\HandlingException; +use Illuminate\Support\Arr; /** * Class Date. @@ -90,12 +91,13 @@ protected function fillDate(): void { $richDate = new RichDate(); - if (isset($this->rawContent['start'])) { + if (Arr::exists($this->rawContent, 'start')) { $startAsIsoString = $this->rawContent['start']; $richDate->setStart(new DateTime($startAsIsoString)); + $richDate->setHasTime($this->isIsoTimeString($startAsIsoString)); } - if (isset($this->rawContent['end'])) { + if (Arr::exists($this->rawContent, 'end')) { $endAsIsoString = $this->rawContent['end']; $richDate->setEnd(new DateTime($endAsIsoString)); } @@ -103,6 +105,12 @@ protected function fillDate(): void $this->content = $richDate; } + // function for checking if ISO datetime string includes time or not + private function isIsoTimeString(string $isoTimeDateString): bool + { + return strpos($isoTimeDateString, 'T') !== false; + } + /** * @return RichDate */ @@ -128,10 +136,18 @@ public function getStart(): DateTime } /** - * @return DateTime + * @return ?DateTime */ - public function getEnd(): DateTime + public function getEnd(): ?DateTime { return $this->getContent()->getEnd(); } + + /** + * @return bool + */ + public function hasTime(): bool + { + return $this->getContent()->hasTime(); + } } diff --git a/src/Entities/PropertyItems/RichDate.php b/src/Entities/PropertyItems/RichDate.php index 8464938..90371d0 100644 --- a/src/Entities/PropertyItems/RichDate.php +++ b/src/Entities/PropertyItems/RichDate.php @@ -61,7 +61,7 @@ public function getStart(): ?DateTime } /** - * @return DateTime + * @return ?DateTime */ public function getEnd(): ?DateTime { @@ -71,7 +71,7 @@ public function getEnd(): ?DateTime /** * @return bool */ - public function getHasTime(): bool + public function hasTime(): bool { return $this->hasTime; } From 0a07217d4b048fd024a7bc80a387c4e88baa7a25 Mon Sep 17 00:00:00 2001 From: johguentner Date: Mon, 11 Jul 2022 10:21:11 +0200 Subject: [PATCH 15/20] modify tests: evaluate 'hasTime' in date props - add date with and without time in page response stub - assert true and false, if time is included - add these two additional properties within assert count of page results --- tests/EndpointPagesTest.php | 16 +++++++++++++--- .../endpoints/pages/response_specific_200.json | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/tests/EndpointPagesTest.php b/tests/EndpointPagesTest.php index 376caa3..cd33a19 100644 --- a/tests/EndpointPagesTest.php +++ b/tests/EndpointPagesTest.php @@ -69,9 +69,13 @@ public function it_returns_page_entity_with_filled_properties() // check properties $this->assertSame('Notion Is Awesome', $pageResult->getTitle()); $this->assertSame('page', $pageResult->getObjectType()); - $this->assertCount(7, $pageResult->getRawProperties()); - $this->assertCount(7, $pageResult->getProperties()); - $this->assertCount(7, $pageResult->getPropertyKeys()); + $this->assertCount(9, $pageResult->getRawProperties()); + $this->assertCount(9, $pageResult->getProperties()); + $this->assertCount(9, $pageResult->getPropertyKeys()); + + // check date and datetime properties + $this->assertTrue($pageResult->getProperty('DateWithTime')->hasTime()); + $this->assertFalse($pageResult->getProperty('DateWithoutTime')->hasTime()); $this->assertInstanceOf(Carbon::class, $pageResult->getCreatedTime()); $this->assertInstanceOf(Carbon::class, $pageResult->getLastEditedTime()); @@ -179,6 +183,7 @@ public function it_assembles_properties_for_a_new_page() $this->assertTrue($dateRangeProp->isRange()); $this->assertEquals($dateRangeStartValue, $dateRangeProp->getStart()); $this->assertEquals($dateRangeEndValue, $dateRangeProp->getEnd()); + $this->assertFalse($dateRangeProp->hasTime()); $this->assertJson($dateRangeProp->asText()); $this->assertStringContainsString($dateRangeStartValue->format('Y-m-d H:i:s'), $dateRangeProp->asText()); $this->assertStringContainsString($dateRangeEndValue->format('Y-m-d H:i:s'), $dateRangeProp->asText()); @@ -200,6 +205,7 @@ public function it_assembles_properties_for_a_new_page() $this->assertTrue($dateTimeRangeProp->isRange()); $this->assertEquals($dateRangeStartValue, $dateTimeRangeProp->getStart()); $this->assertEquals($dateRangeEndValue, $dateTimeRangeProp->getEnd()); + $this->assertTrue($dateTimeRangeProp->hasTime()); $this->assertJson($dateTimeRangeProp->asText()); $this->assertStringContainsString($dateRangeStartValue->format('Y-m-d H:i:s'), $dateTimeRangeProp->asText()); $this->assertStringContainsString($dateRangeEndValue->format('Y-m-d H:i:s'), $dateTimeRangeProp->asText()); @@ -216,6 +222,8 @@ public function it_assembles_properties_for_a_new_page() $this->assertInstanceOf(RichDate::class, $dateProp->getContent()); $this->assertFalse($dateProp->isRange()); $this->assertEquals($dateValue, $dateProp->getStart()); + $this->assertNull($dateProp->getEnd()); + $this->assertFalse($dateProp->hasTime()); $dateContent = $dateProp->getRawContent(); $this->assertArrayHasKey('date', $dateContent); $this->assertCount(1, $dateContent['date']); @@ -227,6 +235,8 @@ public function it_assembles_properties_for_a_new_page() $this->assertInstanceOf(RichDate::class, $dateTimeProp->getContent()); $this->assertFalse($dateTimeProp->isRange()); $this->assertEquals($dateValue, $dateTimeProp->getStart()); + $this->assertNull($dateTimeProp->getEnd()); + $this->assertTrue($dateTimeProp->hasTime()); $dateTimeContent = $dateTimeProp->getRawContent(); $this->assertArrayHasKey('date', $dateTimeContent); $this->assertCount(1, $dateTimeContent['date']); diff --git a/tests/stubs/endpoints/pages/response_specific_200.json b/tests/stubs/endpoints/pages/response_specific_200.json index 0dac766..268e117 100644 --- a/tests/stubs/endpoints/pages/response_specific_200.json +++ b/tests/stubs/endpoints/pages/response_specific_200.json @@ -40,6 +40,24 @@ } ] }, + "DateWithTime":{ + "id": ">d{D", + "type": "date", + "date": { + "start": "2021-05-14T00:00:00.000+00:00", + "end": "2021-06-14T00:00:00.000+00:00", + "time_zone": null + } + }, + "DateWithoutTime":{ + "id": ">c{d", + "type": "date", + "date": { + "start": "2021-05-14", + "end": "2021-06-14", + "time_zone": null + } + }, "SelectColumn": { "id": "nKff", "type": "select", From a77289eae7aa157df3dd5e5c34ba0cdfe09d9e91 Mon Sep 17 00:00:00 2001 From: johguentner Date: Mon, 11 Jul 2022 10:23:26 +0200 Subject: [PATCH 16/20] clarify: move edge-case label to test method --- tests/EndpointDatabaseTest.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/EndpointDatabaseTest.php b/tests/EndpointDatabaseTest.php index ba07609..95c9f1a 100644 --- a/tests/EndpointDatabaseTest.php +++ b/tests/EndpointDatabaseTest.php @@ -158,9 +158,10 @@ public function it_throws_a_notion_exception_bad_request() Notion::database('8284f3ff77e24d4a939d19459e4d6bdc')->query(); } - //# EDGE CASES - - /** @test */ + /** + * @test + * ! edge-case + */ public function it_queries_a_database_with_a_rollup_property_with_empty_selects() { // success /v1/databases/DATABASE_DOES_EXIST/query From 9aa463abddc68c5e6048ad09783f432dc50432d3 Mon Sep 17 00:00:00 2001 From: Di Date: Mon, 11 Jul 2022 10:23:40 +0200 Subject: [PATCH 17/20] Apply fixes from StyleCI (#75) --- tests/EndpointDatabaseTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/EndpointDatabaseTest.php b/tests/EndpointDatabaseTest.php index 95c9f1a..e20a6ca 100644 --- a/tests/EndpointDatabaseTest.php +++ b/tests/EndpointDatabaseTest.php @@ -158,10 +158,10 @@ public function it_throws_a_notion_exception_bad_request() Notion::database('8284f3ff77e24d4a939d19459e4d6bdc')->query(); } - /** - * @test + /** + * @test * ! edge-case - */ + */ public function it_queries_a_database_with_a_rollup_property_with_empty_selects() { // success /v1/databases/DATABASE_DOES_EXIST/query From 2451387d916e1c5a23b3c2dcb587df9a8757b508 Mon Sep 17 00:00:00 2001 From: Di Date: Mon, 11 Jul 2022 10:46:58 +0200 Subject: [PATCH 18/20] Apply fixes from StyleCI (#76) --- tests/EndpointDatabaseTest.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/EndpointDatabaseTest.php b/tests/EndpointDatabaseTest.php index 7e08627..f7e9364 100644 --- a/tests/EndpointDatabaseTest.php +++ b/tests/EndpointDatabaseTest.php @@ -159,7 +159,6 @@ public function it_throws_a_notion_exception_bad_request() Notion::database('8284f3ff77e24d4a939d19459e4d6bdc')->query(); } - /** @test */ public function it_queries_a_database_with_and_without_offset_and_processes_result() { @@ -216,7 +215,6 @@ public function it_queries_a_database_with_and_without_offset_and_processes_resu $page = $resultWithOffsetCollection->first(); $this->assertEquals('Betty Holberton', $page->getTitle()); } - /** * @test @@ -236,7 +234,7 @@ public function it_queries_a_database_with_a_rollup_property_with_empty_selects( $result = Notion::database('11971214ce574df7a58389c1deda61d7')->query(); $this->assertInstanceOf(PageCollection::class, $result); - + $resultCollection = $result->asCollection(); $this->assertIsIterable($resultCollection); From 40ed8acc4f805e95c66228770da6dd5ce4cb5f1a Mon Sep 17 00:00:00 2001 From: johguentner Date: Mon, 1 Aug 2022 18:38:36 +0200 Subject: [PATCH 19/20] fix: check if content is null in date property - return null in helper-functions, if content is null: hasTime, getEnd - throw HandlingException::class, if getStart is called on empty content --- src/Entities/Properties/Date.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Entities/Properties/Date.php b/src/Entities/Properties/Date.php index 8465ded..e810fc7 100644 --- a/src/Entities/Properties/Date.php +++ b/src/Entities/Properties/Date.php @@ -132,6 +132,9 @@ public function isRange(): bool */ public function getStart(): DateTime { + if ($this->getContent() === null) { + throw new HandlingException('Invalid content: The content of the Date Property is null.'); + } return $this->getContent()->getStart(); } @@ -140,6 +143,9 @@ public function getStart(): DateTime */ public function getEnd(): ?DateTime { + if ($this->getContent() === null) { + return null; + } return $this->getContent()->getEnd(); } @@ -148,6 +154,9 @@ public function getEnd(): ?DateTime */ public function hasTime(): bool { + if ($this->getContent() === null) { + return false; + } return $this->getContent()->hasTime(); } } From 196390697c18782ff73ad93fab0ed475ba0ad2b9 Mon Sep 17 00:00:00 2001 From: Di Date: Mon, 1 Aug 2022 18:38:54 +0200 Subject: [PATCH 20/20] Apply fixes from StyleCI (#78) --- src/Entities/Properties/Date.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Entities/Properties/Date.php b/src/Entities/Properties/Date.php index e810fc7..5a7fc1f 100644 --- a/src/Entities/Properties/Date.php +++ b/src/Entities/Properties/Date.php @@ -135,6 +135,7 @@ public function getStart(): DateTime if ($this->getContent() === null) { throw new HandlingException('Invalid content: The content of the Date Property is null.'); } + return $this->getContent()->getStart(); } @@ -146,6 +147,7 @@ public function getEnd(): ?DateTime if ($this->getContent() === null) { return null; } + return $this->getContent()->getEnd(); } @@ -157,6 +159,7 @@ public function hasTime(): bool if ($this->getContent() === null) { return false; } + return $this->getContent()->hasTime(); } }