From da9e8984baa21a677e1e1378f88249177a1e30a2 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Thu, 5 Nov 2026 12:51:27 +0100 Subject: [PATCH] fix(contexts): keep the nodes added by an application update in the loaded shape Updating an application with new tables or views stored the added nodes and start page contents in their JSON shape (nodeType, nodeId, nodeRelId) next to the loaded ones (node_type, node_id, node_rel_id). Ordering the start page then read keys that were not there, which raised an "Undefined array key" warning and left the new contents unordered, and the returned application mixed both shapes. Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Ruben van der Linde --- lib/Service/ContextService.php | 13 ++++++- tests/unit/Service/ContextServiceTest.php | 47 ++++++++++++++++++++++- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/lib/Service/ContextService.php b/lib/Service/ContextService.php index 828897ad6f..f87a2f7f6f 100644 --- a/lib/Service/ContextService.php +++ b/lib/Service/ContextService.php @@ -259,8 +259,17 @@ public function update(int $contextId, string $userId, ?string $name, ?string $i $node['order'] ?? 100, $userId ); - $currentNodes[$addedNode->getId()] = $addedNode->jsonSerialize(); - $currentPages[$updatedContent->getPageId()]['content'][$updatedContent->getId()] = $updatedContent->jsonSerialize(); + // The same shape as the nodes and contents the mapper loads + $currentNodes[$addedNode->getId()] = [ + 'id' => $addedNode->getId(), + 'node_id' => $addedNode->getNodeId(), + 'node_type' => $addedNode->getNodeType(), + 'permissions' => $addedNode->getPermissions(), + ]; + $currentPages[$updatedContent->getPageId()]['content'][$updatedContent->getId()] = [ + 'order' => $updatedContent->getOrder(), + 'node_rel_id' => $updatedContent->getNodeRelId(), + ]; } unset($nodesBeingAdded); diff --git a/tests/unit/Service/ContextServiceTest.php b/tests/unit/Service/ContextServiceTest.php index 67b94c8cca..afcd9beedf 100644 --- a/tests/unit/Service/ContextServiceTest.php +++ b/tests/unit/Service/ContextServiceTest.php @@ -38,6 +38,7 @@ final class ContextServiceTest extends TestCase { private MockObject $pageContentMapper; private MockObject $userManager; private MockObject $auditLogService; + private MockObject $permissionsService; protected function setUp(): void { parent::setUp(); @@ -47,7 +48,7 @@ protected function setUp(): void { $this->pageMapper = $this->createMock(PageMapper::class); $this->pageContentMapper = $this->createMock(PageContentMapper::class); $logger = $this->createMock(LoggerInterface::class); - $permissionsService = $this->createMock(PermissionsService::class); + $this->permissionsService = $this->createMock(PermissionsService::class); $this->userManager = $this->createMock(IUserManager::class); $this->auditLogService = $this->createMock(AuditLogServiceInterface::class); $dbConnection = $this->createMock(IDBConnection::class); @@ -61,7 +62,7 @@ protected function setUp(): void { $this->pageMapper, $this->pageContentMapper, $logger, - $permissionsService, + $this->permissionsService, $this->userManager, $this->auditLogService, $dbConnection, @@ -189,6 +190,48 @@ public function testUpdateReordersStartPageContentsFromSubmittedNodes(): void { self::assertSame(10, $updatedContext->getPages()[301]['content'][502]['order']); } + public function testUpdateGivesAnAddedNodeTheShapeOfTheLoadedNodesAndOrdersIt(): void { + $context = new Context(); + $context->id = 7; + $context->setNodes([ + 101 => ['id' => 101, 'node_type' => 0, 'node_id' => 11, 'permissions' => 1], + ]); + $context->setPages([ + 301 => [ + 'id' => 301, + 'page_type' => Page::TYPE_STARTPAGE, + 'content' => [501 => ['order' => 10, 'node_rel_id' => 101]], + ], + ]); + $this->contextMapper->method('findById')->willReturn($context); + $this->contextMapper->method('update')->willReturnArgument(0); + $this->permissionsService->method('canManageNodeById')->willReturn(true); + $this->contextNodeRelMapper->method('insert')->willReturnCallback(static function (ContextNodeRelation $relation): ContextNodeRelation { + $relation->id = 102; + return $relation; + }); + $this->pageContentMapper->method('insert')->willReturnCallback(static function (PageContent $content): PageContent { + $content->id = 502; + return $content; + }); + $pageContentsById = [ + 501 => $this->buildPageContent(501, 301, 101, 10), + 502 => $this->buildPageContent(502, 301, 102, 100), + ]; + $this->pageContentMapper->method('findById') + ->willReturnCallback(static fn (int $contentId): PageContent => $pageContentsById[$contentId]); + $this->pageContentMapper->method('update')->willReturnArgument(0); + + $updatedContext = $this->service->update(7, 'user-1', null, null, null, [ + ['id' => 12, 'type' => 1, 'permissions' => 3], + ['id' => 11, 'type' => 0, 'permissions' => 1], + ]); + + self::assertSame(['id' => 102, 'node_id' => 12, 'node_type' => 1, 'permissions' => 3], $updatedContext->getNodes()[102]); + self::assertSame(['order' => 10, 'node_rel_id' => 102], $updatedContext->getPages()[301]['content'][502]); + self::assertSame(20, $updatedContext->getPages()[301]['content'][501]['order']); + } + public function testUpdateLogsRemovedNodes(): void { $context = new Context(); $context->id = 7;