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"