API endpoints to delete customer/project/activity (#5181)

* added service methods with events to delete customer, project, activity
* added API endpoints to delete customer, project, activity
* added tests for new API endpoints
This commit is contained in:
Kevin Papst
2024-11-27 15:25:13 +01:00
committed by GitHub
parent f13b81ede7
commit e030ff08db
13 changed files with 408 additions and 80 deletions

View File

@@ -22,6 +22,8 @@ use App\Repository\ActivityRepository;
use App\Repository\Query\VisibilityInterface;
use App\Tests\Mocks\ActivityTestMetaFieldSubscriberMock;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* @group integration
@@ -449,4 +451,62 @@ class ActivityControllerTest extends APIControllerBaseTest
$activity = $em->getRepository(Activity::class)->find(1);
$this->assertEquals('another,testing,bar', $activity->getMetaField('metatestmock')->getValue());
}
// ------------------------------- [DELETE] -------------------------------
public function testDeleteIsSecure(): void
{
$this->assertUrlIsSecured('/api/activities/1', Request::METHOD_DELETE);
}
public function testDeleteActionWithUnknownTimesheet(): void
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$this->assertNotFoundForDelete($client, '/api/activities/' . PHP_INT_MAX);
}
public function testDeleteEntityIsSecure(): void
{
$this->assertUrlIsSecuredForRole(User::ROLE_USER, '/api/activities/1', Request::METHOD_DELETE);
}
public function testDeleteActionWithoutAuthorization(): void
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
$imports = $this->loadActivityTestData();
$this->request($client, '/api/activities/' . $imports[2]->getId(), Request::METHOD_DELETE);
$response = $client->getResponse();
$this->assertApiResponseAccessDenied($response);
}
public function testDeleteAction(): void
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$imports = $this->loadActivityTestData();
$getUrl = '/api/activities/' . $imports[2]->getId();
$this->assertAccessIsGranted($client, $getUrl);
$content = $client->getResponse()->getContent();
self::assertIsString($content);
$result = json_decode($content, true);
self::assertIsArray($result);
self::assertApiResponseTypeStructure('ActivityEntity', $result);
self::assertNotEmpty($result['id']);
self::assertIsNumeric($result['id']);
$id = $result['id'];
$this->request($client, '/api/activities/' . $id, Request::METHOD_DELETE);
$this->assertTrue($client->getResponse()->isSuccessful());
self::assertEquals(Response::HTTP_NO_CONTENT, $client->getResponse()->getStatusCode());
$this->assertEmpty($client->getResponse()->getContent());
$this->request($client, $getUrl);
$this->assertApiException($client->getResponse(), [
'code' => Response::HTTP_NOT_FOUND,
'message' => 'Not Found'
]);
}
}

View File

@@ -22,6 +22,8 @@ use App\Repository\CustomerRateRepository;
use App\Repository\CustomerRepository;
use App\Tests\Mocks\CustomerTestMetaFieldSubscriberMock;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* @group integration
@@ -88,6 +90,45 @@ class CustomerControllerTest extends APIControllerBaseTest
return [$rate1, $rate2];
}
/**
* @return array{0: Customer, 1: Customer}
*/
private function loadCustomerData(): array
{
/** @var CustomerRateRepository $rateRepository */
$rateRepository = $this->getEntityManager()->getRepository(CustomerRate::class);
/** @var CustomerRepository $repository */
$repository = $this->getEntityManager()->getRepository(Customer::class);
$customer1 = new Customer('foooo');
$customer1->setCountry('DE');
$customer1->setTimezone('Europe/Paris');
$repository->saveCustomer($customer1);
$customer2 = new Customer('baaaaar');
$customer2->setCountry('RU');
$customer2->setTimezone('Europe/Moscow');
$repository->saveCustomer($customer2);
$rate1 = new CustomerRate();
$rate1->setCustomer($customer1);
$rate1->setRate(17.45);
$rate1->setIsFixed(false);
$rateRepository->saveRate($rate1);
$rate2 = new CustomerRate();
$rate2->setCustomer($customer1);
$rate2->setRate(99);
$rate2->setInternalRate(9);
$rate2->setIsFixed(true);
$rate2->setUser($this->getUserByName(UserFixtures::USERNAME_USER));
$rateRepository->saveRate($rate2);
return [$customer1, $customer2];
}
public function testIsSecure(): void
{
$this->assertUrlIsSecured('/api/customers');
@@ -392,4 +433,62 @@ class CustomerControllerTest extends APIControllerBaseTest
$customer = $em->getRepository(Customer::class)->find(1);
$this->assertEquals('another,testing,bar', $customer->getMetaField('metatestmock')->getValue());
}
// ------------------------------- [DELETE] -------------------------------
public function testDeleteIsSecure(): void
{
$this->assertUrlIsSecured('/api/customers/1', Request::METHOD_DELETE);
}
public function testDeleteActionWithUnknownTimesheet(): void
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$this->assertNotFoundForDelete($client, '/api/customers/' . PHP_INT_MAX);
}
public function testDeleteEntityIsSecure(): void
{
$this->assertUrlIsSecuredForRole(User::ROLE_USER, '/api/customers/1', Request::METHOD_DELETE);
}
public function testDeleteActionWithoutAuthorization(): void
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
$imports = $this->loadCustomerData();
$this->request($client, '/api/customers/' . $imports[1]->getId(), Request::METHOD_DELETE);
$response = $client->getResponse();
$this->assertApiResponseAccessDenied($response);
}
public function testDeleteAction(): void
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$imports = $this->loadCustomerData();
$getUrl = '/api/customers/' . $imports[0]->getId();
$this->assertAccessIsGranted($client, $getUrl);
$content = $client->getResponse()->getContent();
self::assertIsString($content);
$result = json_decode($content, true);
self::assertIsArray($result);
self::assertApiResponseTypeStructure('CustomerEntity', $result);
self::assertNotEmpty($result['id']);
self::assertIsNumeric($result['id']);
$id = $result['id'];
$this->request($client, '/api/customers/' . $id, Request::METHOD_DELETE);
$this->assertTrue($client->getResponse()->isSuccessful());
self::assertEquals(Response::HTTP_NO_CONTENT, $client->getResponse()->getStatusCode());
$this->assertEmpty($client->getResponse()->getContent());
$this->request($client, $getUrl);
$this->assertApiException($client->getResponse(), [
'code' => Response::HTTP_NOT_FOUND,
'message' => 'Not Found'
]);
}
}

View File

@@ -22,7 +22,8 @@ use App\Repository\ProjectRepository;
use App\Repository\Query\VisibilityInterface;
use App\Tests\Mocks\ProjectTestMetaFieldSubscriberMock;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpKernel\HttpKernelBrowser;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* @group integration
@@ -110,10 +111,14 @@ class ProjectControllerTest extends APIControllerBaseTest
self::assertApiResponseTypeStructure('ProjectCollection', $result[0]);
}
protected function loadProjectTestData(HttpKernelBrowser $client)
/**
* @return array{0: Project, 1: Project, 2: Project, 3: Project, 4: Project}
*/
protected function loadProjectTestData(): array
{
$em = $this->getEntityManager();
/** @var Customer $customer */
$customer = $em->getRepository(Customer::class)->find(1);
$customer2 = new Customer('first one');
@@ -127,49 +132,49 @@ class ProjectControllerTest extends APIControllerBaseTest
$customer3->setTimezone('Europe/Vienna');
$em->persist($customer3);
$project = new Project();
$project->setName('first');
$project->setVisible(false);
$project->setCustomer($customer2);
$em->persist($project);
$project1 = new Project();
$project1->setName('first');
$project1->setVisible(false);
$project1->setCustomer($customer2);
$em->persist($project1);
$project = new Project();
$project->setName('second');
$project->setVisible(false);
$project->setCustomer($customer);
$em->persist($project);
$project2 = new Project();
$project2->setName('second');
$project2->setVisible(false);
$project2->setCustomer($customer);
$em->persist($project2);
$project = new Project();
$project->setName('third');
$project->setVisible(true);
$project->setCustomer($customer2);
$em->persist($project);
$project3 = new Project();
$project3->setName('third');
$project3->setVisible(true);
$project3->setCustomer($customer2);
$em->persist($project3);
$project = new Project();
$project->setName('fourth');
$project->setVisible(true);
$project->setCustomer($customer3);
$em->persist($project);
$project4 = new Project();
$project4->setName('fourth');
$project4->setVisible(true);
$project4->setCustomer($customer3);
$em->persist($project4);
$project = new Project();
$project->setName('fifth');
$project->setVisible(true);
$project->setCustomer($customer);
$project5 = new Project();
$project5->setName('fifth');
$project5->setVisible(true);
$project5->setCustomer($customer);
// add meta fields
$meta = new ProjectMeta();
$meta->setName('bar')->setValue('foo')->setIsVisible(false);
$project->setMetaField($meta);
$project5->setMetaField($meta);
$meta = new ProjectMeta();
$meta->setName('foo')->setValue('bar')->setIsVisible(true);
$project->setMetaField($meta);
$em->persist($project);
$project5->setMetaField($meta);
$em->persist($project5);
// and a team
$team = new Team('Testing project team');
$team->addTeamlead($this->getUserByRole(User::ROLE_USER));
$team->addCustomer($customer);
$team->addProject($project);
$team->addProject($project5);
$team->addUser($this->getUserByRole(User::ROLE_TEAMLEAD));
$em->persist($team);
@@ -178,18 +183,24 @@ class ProjectControllerTest extends APIControllerBaseTest
$em->flush();
return [$customer, $customer2, $customer3];
return [
$project1,
$project2,
$project3,
$project4,
$project5,
];
}
/**
* @dataProvider getCollectionTestData
*/
public function testGetCollectionWithParams($url, $customer, $parameters, $expected): void
public function testGetCollectionWithParams(string $url, ?int $project, array $parameters, array $expected): void
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$imports = $this->loadProjectTestData($client);
$imports = $this->loadProjectTestData();
$customerId = $customer !== null ? $imports[$customer]->getId() : null;
$customerId = $project !== null ? $imports[$project]->getCustomer()?->getId() : null;
if ($customerId !== null) {
if (\array_key_exists('customer', $parameters)) {
@@ -236,19 +247,19 @@ class ProjectControllerTest extends APIControllerBaseTest
{
// if you wonder why: case-sensitive ordering feels strange ... "Title" > "fifth”
yield ['/api/projects', null, [], [[true, 1], [false, 1], [false, 3]]];
yield ['/api/projects', 0, ['customer' => '1'], [[true, 1], [false, 1]]];
yield ['/api/projects', 0, ['customer' => '1', 'visible' => VisibilityInterface::SHOW_VISIBLE], [[true, 1], [false, 1]]];
yield ['/api/projects', 0, ['customer' => '1', 'visible' => VisibilityInterface::SHOW_BOTH], [[true, 1], [false, 1], [false, 1]]];
yield ['/api/projects', 0, ['customer' => '1', 'visible' => VisibilityInterface::SHOW_HIDDEN], [[false, 1]]];
yield ['/api/projects', 1, ['customer' => '1'], [[true, 1], [false, 1]]];
yield ['/api/projects', 1, ['customer' => '1', 'visible' => VisibilityInterface::SHOW_VISIBLE], [[true, 1], [false, 1]]];
yield ['/api/projects', 1, ['customer' => '1', 'visible' => VisibilityInterface::SHOW_BOTH], [[true, 1], [false, 1], [false, 1]]];
yield ['/api/projects', 1, ['customer' => '1', 'visible' => VisibilityInterface::SHOW_HIDDEN], [[false, 1]]];
// customer is invisible => query only returns results for VisibilityInterface::SHOW_BOTH
yield ['/api/projects', 1, ['customer' => '2', 'visible' => VisibilityInterface::SHOW_VISIBLE], []];
yield ['/api/projects', 1, ['customer' => '2', 'visible' => VisibilityInterface::SHOW_BOTH], [[false, 2], [false, 2]]];
yield ['/api/projects', 1, ['customer' => '2', 'customers' => ['2'], 'visible' => VisibilityInterface::SHOW_BOTH], [[false, 2], [false, 2]]];
yield ['/api/projects', 1, ['customers' => ['2', '2'], 'visible' => VisibilityInterface::SHOW_BOTH], [[false, 2], [false, 2]]];
yield ['/api/projects', 1, ['customer' => '2', 'visible' => VisibilityInterface::SHOW_HIDDEN], []];
yield ['/api/projects', 1, ['customer' => '2', 'visible' => VisibilityInterface::SHOW_HIDDEN, 'start' => '2010-12-11', 'end' => '2030-12-11'], []];
yield ['/api/projects', 1, ['customers' => ['2'], 'visible' => VisibilityInterface::SHOW_HIDDEN, 'start' => '2010-12-11', 'end' => '2030-12-11'], []];
yield ['/api/projects', 1, ['customers' => ['2', '2'], 'visible' => VisibilityInterface::SHOW_HIDDEN, 'start' => '2010-12-11', 'end' => '2030-12-11'], []];
yield ['/api/projects', 0, ['customer' => '2', 'visible' => VisibilityInterface::SHOW_VISIBLE], []];
yield ['/api/projects', 0, ['customer' => '2', 'visible' => VisibilityInterface::SHOW_BOTH], [[false, 2], [false, 2]]];
yield ['/api/projects', 0, ['customer' => '2', 'customers' => ['2'], 'visible' => VisibilityInterface::SHOW_BOTH], [[false, 2], [false, 2]]];
yield ['/api/projects', 0, ['customers' => ['2', '2'], 'visible' => VisibilityInterface::SHOW_BOTH], [[false, 2], [false, 2]]];
yield ['/api/projects', 0, ['customer' => '2', 'visible' => VisibilityInterface::SHOW_HIDDEN], []];
yield ['/api/projects', 0, ['customer' => '2', 'visible' => VisibilityInterface::SHOW_HIDDEN, 'start' => '2010-12-11', 'end' => '2030-12-11'], []];
yield ['/api/projects', 0, ['customers' => ['2'], 'visible' => VisibilityInterface::SHOW_HIDDEN, 'start' => '2010-12-11', 'end' => '2030-12-11'], []];
yield ['/api/projects', 0, ['customers' => ['2', '2'], 'visible' => VisibilityInterface::SHOW_HIDDEN, 'start' => '2010-12-11', 'end' => '2030-12-11'], []];
}
public function testGetEntityIsSecure(): void
@@ -599,4 +610,62 @@ class ProjectControllerTest extends APIControllerBaseTest
$project = $em->getRepository(Project::class)->find(1);
$this->assertEquals('another,testing,bar', $project->getMetaField('metatestmock')->getValue());
}
// ------------------------------- [DELETE] -------------------------------
public function testDeleteIsSecure(): void
{
$this->assertUrlIsSecured('/api/projects/1', Request::METHOD_DELETE);
}
public function testDeleteActionWithUnknownTimesheet(): void
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$this->assertNotFoundForDelete($client, '/api/projects/' . PHP_INT_MAX);
}
public function testDeleteEntityIsSecure(): void
{
$this->assertUrlIsSecuredForRole(User::ROLE_USER, '/api/projects/1', Request::METHOD_DELETE);
}
public function testDeleteActionWithoutAuthorization(): void
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
$imports = $this->loadProjectTestData();
$this->request($client, '/api/projects/' . $imports[2]->getId(), Request::METHOD_DELETE);
$response = $client->getResponse();
$this->assertApiResponseAccessDenied($response);
}
public function testDeleteAction(): void
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$imports = $this->loadProjectTestData();
$getUrl = '/api/projects/' . $imports[2]->getId();
$this->assertAccessIsGranted($client, $getUrl);
$content = $client->getResponse()->getContent();
self::assertIsString($content);
$result = json_decode($content, true);
self::assertIsArray($result);
self::assertApiResponseTypeStructure('ProjectEntity', $result);
self::assertNotEmpty($result['id']);
self::assertIsNumeric($result['id']);
$id = $result['id'];
$this->request($client, '/api/projects/' . $id, Request::METHOD_DELETE);
$this->assertTrue($client->getResponse()->isSuccessful());
self::assertEquals(Response::HTTP_NO_CONTENT, $client->getResponse()->getStatusCode());
$this->assertEmpty($client->getResponse()->getContent());
$this->request($client, $getUrl);
$this->assertApiException($client->getResponse(), [
'code' => Response::HTTP_NOT_FOUND,
'message' => 'Not Found'
]);
}
}