From 7b00006943e484e073bafb310590068d315deecd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 14 Apr 2026 15:35:22 +0200 Subject: [PATCH] Add `StoredObjectLockApiController` and tests for lock removal functionality - Implemented `StoredObjectLockApiController` with endpoint to remove locks from stored objects. - Added access control checks and validation for lock existence before removal. - Wrote `StoredObjectLockApiControllerTest` to cover access denial, lock absence, and successful lock removal scenarios. - Utilized `MockClock` in test cases to accurately simulate time-based behaviors. --- .../StoredObjectLockApiController.php | 47 ++++++++ .../StoredObjectLockApiControllerTest.php | 100 ++++++++++++++++++ .../ChillDocStoreBundle/chill.api.specs.yaml | 23 ++++ 3 files changed, 170 insertions(+) create mode 100644 src/Bundle/ChillDocStoreBundle/Controller/StoredObjectLockApiController.php create mode 100644 src/Bundle/ChillDocStoreBundle/Tests/Controller/StoredObjectLockApiControllerTest.php diff --git a/src/Bundle/ChillDocStoreBundle/Controller/StoredObjectLockApiController.php b/src/Bundle/ChillDocStoreBundle/Controller/StoredObjectLockApiController.php new file mode 100644 index 000000000..573bb40da --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Controller/StoredObjectLockApiController.php @@ -0,0 +1,47 @@ +security->isGranted(StoredObjectRoleEnum::EDIT->value, $storedObject)) { + throw new AccessDeniedHttpException(); + } + + if (!$this->storedObjectLockManager->hasLock($storedObject)) { + throw new PreconditionFailedHttpException('No lock found for this stored object'); + } + + $this->storedObjectLockManager->deleteLock($storedObject, $this->clock->now()); + + return new Response(null, Response::HTTP_NO_CONTENT); + } +} diff --git a/src/Bundle/ChillDocStoreBundle/Tests/Controller/StoredObjectLockApiControllerTest.php b/src/Bundle/ChillDocStoreBundle/Tests/Controller/StoredObjectLockApiControllerTest.php new file mode 100644 index 000000000..5970a9ea8 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Tests/Controller/StoredObjectLockApiControllerTest.php @@ -0,0 +1,100 @@ +prophesize(Security::class); + $lockManager = $this->prophesize(StoredObjectLockManager::class); + $clock = new MockClock(); + $storedObject = new StoredObject(); + + $security->isGranted(StoredObjectRoleEnum::EDIT, $storedObject)->willReturn(false); + + $controller = new StoredObjectLockApiController( + $security->reveal(), + $lockManager->reveal(), + $clock + ); + + $this->expectException(AccessDeniedHttpException::class); + + $controller->removeLock($storedObject); + } + + public function testRemoveLockNoLockFound(): void + { + $security = $this->prophesize(Security::class); + $lockManager = $this->prophesize(StoredObjectLockManager::class); + $clock = new MockClock(); + $storedObject = new StoredObject(); + + $security->isGranted(StoredObjectRoleEnum::EDIT, $storedObject)->willReturn(true); + $lockManager->hasLock($storedObject)->willReturn(false); + + $controller = new StoredObjectLockApiController( + $security->reveal(), + $lockManager->reveal(), + $clock + ); + + $this->expectException(PreconditionFailedHttpException::class); + $this->expectExceptionMessage('No lock found for this stored object'); + + $controller->removeLock($storedObject); + } + + public function testRemoveLockSuccess(): void + { + $security = $this->prophesize(Security::class); + $lockManager = $this->prophesize(StoredObjectLockManager::class); + $clock = new MockClock('2024-01-01 12:00:00'); + $storedObject = new StoredObject(); + + $security->isGranted(StoredObjectRoleEnum::EDIT, $storedObject)->willReturn(true); + $lockManager->hasLock($storedObject)->willReturn(true); + + $lockManager->deleteLock($storedObject, $clock->now())->shouldBeCalled(); + + $controller = new StoredObjectLockApiController( + $security->reveal(), + $lockManager->reveal(), + $clock + ); + + $response = $controller->removeLock($storedObject); + + self::assertSame(Response::HTTP_ACCEPTED, $response->getStatusCode()); + self::assertNull($response->getContent() ?: null); + } +} diff --git a/src/Bundle/ChillDocStoreBundle/chill.api.specs.yaml b/src/Bundle/ChillDocStoreBundle/chill.api.specs.yaml index 8570116aa..327b2c630 100644 --- a/src/Bundle/ChillDocStoreBundle/chill.api.specs.yaml +++ b/src/Bundle/ChillDocStoreBundle/chill.api.specs.yaml @@ -196,3 +196,26 @@ paths: $ref: '#/components/schemas/GenericDoc' type: object + /1.0/doc-store/stored-object/{uuid}/lock: + delete: + tags: + - storedobject + summary: Force removing a lock on a stored object + parameters: + - in: path + name: uuid + required: true + allowEmptyValue: false + description: The UUID of the storedObjeect + schema: + type: string + format: uuid + responses: + 204: + description: "No content" + 403: + description: "Forbidden" + 404: + description: "Not found" + 412: + description: "No lock found for this stored object"