Skip to content

Commit

Permalink
Remove model visa (#966)
Browse files Browse the repository at this point in the history
  • Loading branch information
mmarchois authored Sep 19, 2024
1 parent d37d907 commit bf372f8
Show file tree
Hide file tree
Showing 10 changed files with 317 additions and 35 deletions.
15 changes: 15 additions & 0 deletions src/Application/VisaModel/Command/DeleteVisaModelCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace App\Application\VisaModel\Command;

use App\Application\CommandInterface;

final class DeleteVisaModelCommand implements CommandInterface
{
public function __construct(
public readonly string $uuid,
) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace App\Application\VisaModel\Command;

use App\Domain\VisaModel\Exception\VisaModelNotFoundException;
use App\Domain\VisaModel\Repository\VisaModelRepositoryInterface;
use App\Domain\VisaModel\VisaModel;

final class DeleteVisaModelCommandHandler
{
public function __construct(
private VisaModelRepositoryInterface $visaModelRepository,
) {
}

public function __invoke(DeleteVisaModelCommand $command): void
{
$visaModel = $this->visaModelRepository->findOneByUuid($command->uuid);
if (!$visaModel instanceof VisaModel) {
throw new VisaModelNotFoundException();
}

$this->visaModelRepository->remove($visaModel);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ public function findOneByUuid(string $uuid): ?VisaModel;
public function findOrganizationVisaModels(string $organizationUuid): array;

public function add(VisaModel $visaModel): VisaModel;

public function remove(VisaModel $visaModel): void;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
use App\Infrastructure\Security\Voter\OrganizationVoter;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
Expand All @@ -39,7 +38,7 @@ public function __construct(
methods: ['DELETE'],
)]
#[IsCsrfTokenValid('delete-user')]
public function __invoke(Request $request, string $organizationUuid, string $uuid): RedirectResponse
public function __invoke(string $organizationUuid, string $uuid): RedirectResponse
{
try {
$organizationUser = $this->queryBus->handle(new GetOrganizationUserQuery($organizationUuid, $uuid));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace App\Infrastructure\Controller\MyArea\Organization\VisaModel;

use App\Application\CommandBusInterface;
use App\Application\QueryBusInterface;
use App\Application\VisaModel\Command\DeleteVisaModelCommand;
use App\Domain\VisaModel\Exception\VisaModelNotFoundException;
use App\Infrastructure\Controller\MyArea\Organization\AbstractOrganizationController;
use App\Infrastructure\Security\Voter\OrganizationVoter;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Requirement\Requirement;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Http\Attribute\IsCsrfTokenValid;

final class DeleteVisaModelController extends AbstractOrganizationController
{
public function __construct(
private RouterInterface $router,
private CommandBusInterface $commandBus,
QueryBusInterface $queryBus,
Security $security,
) {
parent::__construct($queryBus, $security);
}

#[Route(
'/organizations/{organizationUuid}/visa_models/{uuid}',
name: 'app_config_visa_models_delete',
requirements: ['organizationUuid' => Requirement::UUID, 'uuid' => Requirement::UUID],
methods: ['DELETE'],
)]
#[IsCsrfTokenValid('delete-visa-model')]
public function __invoke(string $organizationUuid, string $uuid): RedirectResponse
{
$organization = $this->getOrganization($organizationUuid);

if (!$this->security->isGranted(OrganizationVoter::EDIT, $organization)) {
throw new AccessDeniedHttpException();
}

try {
$this->commandBus->handle(new DeleteVisaModelCommand($uuid));
} catch (VisaModelNotFoundException) {
throw new NotFoundHttpException();
}

return new RedirectResponse(
url: $this->router->generate('app_config_visa_models_list', ['uuid' => $organizationUuid]),
status: Response::HTTP_SEE_OTHER,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ public function add(VisaModel $visaModel): VisaModel
return $visaModel;
}

public function remove(VisaModel $visaModel): void
{
$this->getEntityManager()->remove($visaModel);
}

public function findOneByUuid(string $uuid): ?VisaModel
{
return $this->createQueryBuilder('v')
Expand Down
90 changes: 57 additions & 33 deletions templates/my_area/organization/visa_model/index.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
{{'visa.list.title'|trans }} - {{ parent() }}
{% endblock %}

{% set deleteCsrfToken = csrf_token('delete-visa-model') %}

{% block body %}
<section class="fr-container fr-py-5w" aria-labelledby="visa-list">
<div class="fr-grid-row fr-grid-row--gutters">
Expand All @@ -25,41 +27,63 @@
{% endif %}
</div>
<p class="fr-text--sm fr-mt-2w">{{ 'visa.list.help'|trans }}</p>
<div class="fr-table fr-table--no-scroll" id="table-md-component">
<div class="fr-table__wrapper">
<div class="fr-table__container">
<div class="fr-table__content">
<table id="table-md">
<thead>
<tr>
<th scope="col">{{ 'visa.name'|trans }}</th>
<th scope="col">{{ 'visa.description'|trans }}</th>
<th scope="col" class="app-table__actions">{{ 'common.actions'|trans }}</th>
</tr>
</thead>
<tbody data-testid="visa-list">
{% for visa in visaModels %}
<tr>
<td>{{ visa.name }}</td>
<td>{{ visa.description }}</td>
<td>
{% if is_granted(constant('App\\Infrastructure\\Security\\Voter\\OrganizationVoter::EDIT'), organization) %}
<div class="fr-btns-group fr-btns-group--inline-sm">
<a title="{{ 'common.update'|trans }}" href="{{ path('app_config_visa_models_edit', { uuid: visa.uuid, organizationUuid: organization.uuid }) }}" class="fr-btn fr-icon-edit-line fr-btn--tertiary-no-outline">
{{ 'common.update'|trans }}
</a>
</div>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<div class="fr-table fr-table--layout-fixed fr-table--no-caption">
<table>
<thead>
<tr>
<th scope="col">{{ 'visa.name'|trans }}</th>
<th scope="col">{{ 'visa.description'|trans }}</th>
<th scope="col" class="app-table__actions">{{ 'common.actions'|trans }}</th>
</tr>
</thead>
<tbody data-testid="visa-list">
{% for visa in visaModels %}
<tr>
<td>{{ visa.name }}</td>
<td>{{ visa.description }}</td>
<td>
{% if is_granted(constant('App\\Infrastructure\\Security\\Voter\\OrganizationVoter::EDIT'), organization) %}
<div class="fr-btns-group fr-btns-group--inline-sm">
<a title="{{ 'common.update'|trans }}" href="{{ path('app_config_visa_models_edit', { uuid: visa.uuid, organizationUuid: organization.uuid }) }}" class="fr-btn fr-icon-edit-line fr-btn--tertiary-no-outline">
{{ 'common.update'|trans }}
</a>
<form
method="delete"
action="{{ path('app_config_visa_models_delete', { uuid: visa.uuid, organizationUuid: organization.uuid }) }}"
data-controller="form-submit"
data-action="modal-trigger:submit->form-submit#submit"
>
<d-modal-trigger modal="visa-delete-modal" submitValue="visa-delete-{{ visa.uuid }}">
<button
class="fr-btn fr-btn--tertiary-no-outline fr-icon-delete-bin-line"
aria-controls="visa-delete-modal"
aria-label="{{ 'visa.list.delete'|trans({'%name%': visa.name}) }}"
title="{{ 'visa.list.delete'|trans({'%name%': visa.name}) }}"
></button>
</d-modal-trigger>
<input type="hidden" name="_token" value="{{ deleteCsrfToken }}" />
</form>
</div>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</section>
{% endblock %}

{% block body_end %}
{{ parent() }}
{% include 'common/confirmation_modal.html.twig' with {
id: 'visa-delete-modal',
title: 'visa.delete_modal.title'|trans,
buttons: [
{ label: 'common.delete'|trans, attr: {type: 'submit', class: 'fr-btn'} },
{ label: 'common.do_not_delete'|trans, attr: {value: 'close', class: 'fr-btn fr-btn--secondary'} },
]
} only %}
{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

declare(strict_types=1);

namespace App\Tests\Integration\Infrastructure\Controller\MyArea\Organization\VisaModel;

use App\Infrastructure\Persistence\Doctrine\Fixtures\OrganizationFixture;
use App\Tests\Integration\Infrastructure\Controller\AbstractWebTestCase;
use App\Tests\SessionHelper;

final class DeleteVisaModelControllerTest extends AbstractWebTestCase
{
use SessionHelper;

public function testDelete(): void
{
$client = $this->login('[email protected]');
$client->request('DELETE', '/mon-espace/organizations/' . OrganizationFixture::MAIN_ORG_ID . '/visa_models/7eca6579-c07e-4e8e-8f10-fda610d7ee73', [
'_token' => $this->generateCsrfToken($client, 'delete-visa-model'),
]);

$this->assertResponseStatusCodeSame(303);
$client->followRedirect();

$this->assertResponseStatusCodeSame(200);
$this->assertRouteSame('app_config_visa_models_list');
}

public function testNotFound(): void
{
$client = $this->login('[email protected]');
$client->request('DELETE', '/mon-espace/organizations/' . OrganizationFixture::MAIN_ORG_ID . '/visa_models/e18d61be-1797-4d6b-aa58-cd75e623a821', [
'_token' => $this->generateCsrfToken($client, 'delete-visa-model'),
]);

$this->assertResponseStatusCodeSame(404);
}

public function testOrganizationNotOwned(): void
{
$client = $this->login();
$client->request('DELETE', '/mon-espace/organizations/' . OrganizationFixture::MAIN_ORG_ID . '/visa_models/7eca6579-c07e-4e8e-8f10-fda610d7ee73', [
'_token' => $this->generateCsrfToken($client, 'delete-visa-model'),
]);

$this->assertResponseStatusCodeSame(403);
}

public function testBadAccessToken(): void
{
$client = $this->login('[email protected]');
$client->request('DELETE', '/mon-espace/organizations/' . OrganizationFixture::MAIN_ORG_ID . '/visa_models/7eca6579-c07e-4e8e-8f10-fda610d7ee73', [
'_token' => 'abc',
]);

$this->assertResponseRedirects('http://localhost/login', 302);
}

public function testOrganizationOrUserNotFound(): void
{
$client = $this->login();
$client->request('DELETE', '/mon-espace/organizations/f5c1cea8-a61d-43a7-9b5d-4b8c9557c673/visa_models/7eca6579-c07e-4e8e-8f10-fda610d7ee73', [
'_token' => $this->generateCsrfToken($client, 'delete-visa-model'),
]);
$this->assertResponseStatusCodeSame(404);
}

public function testWithoutAuthenticatedUser(): void
{
$client = static::createClient();
$client->request('DELETE', '/mon-espace/organizations/' . OrganizationFixture::MAIN_ORG_ID . '/visa_models/7eca6579-c07e-4e8e-8f10-fda610d7ee73');
$this->assertResponseRedirects('http://localhost/login', 302);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

declare(strict_types=1);

namespace App\Tests\Unit\Application\VisaModel\Command;

use App\Application\VisaModel\Command\DeleteVisaModelCommand;
use App\Application\VisaModel\Command\DeleteVisaModelCommandHandler;
use App\Domain\VisaModel\Exception\VisaModelNotFoundException;
use App\Domain\VisaModel\Repository\VisaModelRepositoryInterface;
use App\Domain\VisaModel\VisaModel;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

final class DeleteVisaModelCommandHandlerTest extends TestCase
{
private MockObject $visaModelRepository;

public function setUp(): void
{
$this->visaModelRepository = $this->createMock(VisaModelRepositoryInterface::class);
}

public function testRemove(): void
{
$visaModel = $this->createMock(VisaModel::class);

$this->visaModelRepository
->expects(self::once())
->method('findOneByUuid')
->with('f8216679-5a0b-4dd5-9e2b-b382d298c3b4')
->willReturn($visaModel);

$this->visaModelRepository
->expects(self::once())
->method('remove')
->with($visaModel);

$handler = new DeleteVisaModelCommandHandler(
$this->visaModelRepository,
);
$command = new DeleteVisaModelCommand('f8216679-5a0b-4dd5-9e2b-b382d298c3b4');

$handler($command);
}

public function testNotFound(): void
{
$this->expectException(VisaModelNotFoundException::class);

$this->visaModelRepository
->expects(self::once())
->method('findOneByUuid')
->with('f8216679-5a0b-4dd5-9e2b-b382d298c3b4')
->willReturn(null);

$this->visaModelRepository
->expects(self::never())
->method('remove');

$handler = new DeleteVisaModelCommandHandler(
$this->visaModelRepository,
);
$command = new DeleteVisaModelCommand('f8216679-5a0b-4dd5-9e2b-b382d298c3b4');

$handler($command);
}
}
Loading

0 comments on commit bf372f8

Please sign in to comment.