feat: add supervisor role
This commit is contained in:
1 parent
dcd85bb911
commit
3af326251d
22 files changed
+270
-103
No files matched your search
@@ -82,6 +82,46 @@ class AccessCodeControllerTest extends WebTestCase
|
||||
self::assertStringContainsString('Administration', $card->text());
|
||||
}
|
||||
|
||||
/**
|
||||
* A supervisor code is read by supervisors and by administrators, who hold no
|
||||
* ROLE_SUPERVISOR of their own -- role_hierarchy does not reach the stored roles the query
|
||||
* filters on. And a supervisor is not an administrator: the admin-only code stays out of
|
||||
* their response.
|
||||
*/
|
||||
public function testASupervisorCodeIsReadBySupervisorsAndAdminsOnly(): void
|
||||
{
|
||||
$this->code('Management office', '5555', visibility: CodeVisibility::SUPERVISOR);
|
||||
$this->code('Serverraum', '9999', visibility: CodeVisibility::ADMIN);
|
||||
$this->em->flush();
|
||||
|
||||
$this->client->loginUser($this->user('[email protected]'));
|
||||
$this->client->request('GET', '/access-codes');
|
||||
self::assertStringNotContainsString('5555', (string) $this->client->getResponse()->getContent());
|
||||
|
||||
$this->client->loginUser($this->user('[email protected]', ['ROLE_EMPLOYEE', 'ROLE_SUPERVISOR']));
|
||||
$card = $this->client->request('GET', '/access-codes')->filter('[data-access-code]');
|
||||
self::assertCount(1, $card);
|
||||
self::assertSame('5555', $card->filter('[data-code]')->text());
|
||||
// The visibility caption the page shows, which is German like all user-facing text.
|
||||
self::assertStringContainsString('Leitung', $card->text());
|
||||
self::assertStringNotContainsString('9999', (string) $this->client->getResponse()->getContent());
|
||||
|
||||
$this->client->loginUser($this->user('[email protected]', ['ROLE_ADMIN']));
|
||||
self::assertCount(2, $this->client->request('GET', '/access-codes')->filter('[data-access-code]'));
|
||||
}
|
||||
|
||||
/**
|
||||
* A supervisor reads codes but does not manage them.
|
||||
*/
|
||||
public function testASupervisorIsNotOfferedTheTable(): void
|
||||
{
|
||||
$this->client->loginUser($this->user('[email protected]', ['ROLE_EMPLOYEE', 'ROLE_SUPERVISOR']));
|
||||
|
||||
$crawler = $this->client->request('GET', '/access-codes');
|
||||
|
||||
self::assertCount(0, $crawler->filter('a[href="/admin/access-codes"]'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotating a code. Only the one in force is shown: its predecessor is history, and its
|
||||
* successor does not open anything yet -- both would only be another number to mistype.
|
||||
|
||||
+114
-27
@@ -2,24 +2,29 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Controller\Admin;
|
||||
namespace App\Tests\Controller\Manage;
|
||||
|
||||
use App\Entity\Booking;
|
||||
use App\Entity\BookingSeries;
|
||||
use App\Entity\Resource;
|
||||
use App\Entity\User;
|
||||
use App\Enum\RecurrenceRule;
|
||||
use App\Enum\ResourceType;
|
||||
use App\Model\TimeRange;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* The admin area, and the widened ownership checks it leans on.
|
||||
* The overview of everybody's bookings, and the widened ownership checks it leans on. Most tests
|
||||
* sign in as an administrator, who reaches it through role_hierarchy; the supervisor ones below
|
||||
* pin down that the role itself is enough, and that it reaches no further than this.
|
||||
*
|
||||
* The test that matters most here is the negative one: releasing somebody else's booking must
|
||||
* still be refused for an ordinary employee. Everything else on this screen is a list.
|
||||
*/
|
||||
class BookingAdminControllerTest extends WebTestCase
|
||||
class BookingManageControllerTest extends WebTestCase
|
||||
{
|
||||
private KernelBrowser $client;
|
||||
private EntityManagerInterface $em;
|
||||
@@ -38,21 +43,21 @@ class BookingAdminControllerTest extends WebTestCase
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function testAnEmployeeCannotReachTheAdminList(): void
|
||||
public function testAnEmployeeCannotReachTheOverview(): void
|
||||
{
|
||||
$this->client->loginUser($this->user('[email protected]'));
|
||||
|
||||
$this->client->request('GET', '/admin/bookings');
|
||||
$this->client->request('GET', '/manage/bookings');
|
||||
|
||||
self::assertSame(Response::HTTP_FORBIDDEN, $this->client->getResponse()->getStatusCode());
|
||||
}
|
||||
|
||||
public function testTheAdminListShowsOtherPeoplesBookings(): void
|
||||
public function testTheOverviewShowsOtherPeoplesBookings(): void
|
||||
{
|
||||
$this->booking($this->user('[email protected]', 'Bea Berger'), $this->resource('D1'));
|
||||
$this->client->loginUser($this->admin());
|
||||
|
||||
$crawler = $this->client->request('GET', '/admin/bookings');
|
||||
$crawler = $this->client->request('GET', '/manage/bookings');
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertStringContainsString('Bea Berger', $crawler->filter('body')->text());
|
||||
@@ -66,15 +71,15 @@ class BookingAdminControllerTest extends WebTestCase
|
||||
|
||||
// The two-step flow a browser walks: the list opens the confirmation modal, the modal
|
||||
// carries the token and posts back to the same URI.
|
||||
$crawler = $this->client->request('GET', '/admin/bookings');
|
||||
self::assertCount(1, $crawler->filter(\sprintf('[hx-get="/bookings/%d/release?return=admin"]', $id)));
|
||||
$crawler = $this->client->request('GET', '/manage/bookings');
|
||||
self::assertCount(1, $crawler->filter(\sprintf('[hx-get="/bookings/%d/release?return=manage"]', $id)));
|
||||
|
||||
$uri = \sprintf('/bookings/%d/release?return=admin', $id);
|
||||
$uri = \sprintf('/bookings/%d/release?return=manage', $id);
|
||||
$this->client->request('POST', $uri, ['_token' => $this->confirmationToken($uri)], server: ['HTTP_HX-Request' => 'true']);
|
||||
|
||||
// ?return= means a genuine navigation rather than a board swap: this page has no board
|
||||
// fragments, so HX-Redirect is the honest answer.
|
||||
self::assertSame('/admin/bookings', $this->client->getResponse()->headers->get('HX-Redirect'));
|
||||
self::assertSame('/manage/bookings', $this->client->getResponse()->headers->get('HX-Redirect'));
|
||||
$this->em->clear();
|
||||
self::assertNull($this->em->getRepository(Booking::class)->find($id));
|
||||
}
|
||||
@@ -95,7 +100,7 @@ class BookingAdminControllerTest extends WebTestCase
|
||||
// The ownership guard runs before the CSRF one, so asserting on its message is what
|
||||
// distinguishes "refused because it is not yours" from "refused because of the token".
|
||||
self::assertStringContainsString(
|
||||
'released by the person holding it or by an administrator',
|
||||
'released by the person holding it or by a supervisor',
|
||||
(string) $this->client->getResponse()->getContent(),
|
||||
);
|
||||
self::assertNotNull($this->em->getRepository(Booking::class)->find($id));
|
||||
@@ -122,7 +127,7 @@ class BookingAdminControllerTest extends WebTestCase
|
||||
$this->booking($this->user('[email protected]', 'Carl Conrad'), $this->resource('D2'));
|
||||
$this->client->loginUser($this->admin());
|
||||
|
||||
$crawler = $this->client->request('GET', '/admin/bookings?user='.$bea->getId());
|
||||
$crawler = $this->client->request('GET', '/manage/bookings?user='.$bea->getId());
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
$text = $crawler->filter('tbody')->text();
|
||||
@@ -136,7 +141,7 @@ class BookingAdminControllerTest extends WebTestCase
|
||||
$this->booking($this->user('[email protected]', 'Carl Conrad'), $this->resource('P1', ResourceType::PARKING));
|
||||
$this->client->loginUser($this->admin());
|
||||
|
||||
$crawler = $this->client->request('GET', '/admin/bookings?type=parking');
|
||||
$crawler = $this->client->request('GET', '/manage/bookings?type=parking');
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
$text = $crawler->filter('tbody')->text();
|
||||
@@ -156,11 +161,11 @@ class BookingAdminControllerTest extends WebTestCase
|
||||
|
||||
$this->client->loginUser($this->user('[email protected]'));
|
||||
$crawler = $this->client->request('GET', '/book/desk?date='.$date);
|
||||
self::assertCount(0, $crawler->filter('[data-spot][hx-get*="/admin/spot/"]'));
|
||||
self::assertCount(0, $crawler->filter('[data-spot][hx-get*="/manage/spot/"]'));
|
||||
|
||||
$this->client->loginUser($this->admin());
|
||||
$crawler = $this->client->request('GET', '/book/desk?date='.$date);
|
||||
self::assertCount(1, $crawler->filter(\sprintf('[data-spot][hx-get="/admin/spot/%d/%s"]', $desk->getId(), $date)));
|
||||
self::assertCount(1, $crawler->filter(\sprintf('[data-spot][hx-get="/manage/spot/%d/%s"]', $desk->getId(), $date)));
|
||||
}
|
||||
|
||||
public function testTheSpotModalListsTheHolderAndReleasesTheBooking(): void
|
||||
@@ -171,7 +176,7 @@ class BookingAdminControllerTest extends WebTestCase
|
||||
|
||||
$crawler = $this->client->request(
|
||||
'GET',
|
||||
\sprintf('/admin/spot/%d/%s', $desk->getId(), $booking->getDate()->format('Y-m-d')),
|
||||
\sprintf('/manage/spot/%d/%s', $desk->getId(), $booking->getDate()->format('Y-m-d')),
|
||||
server: ['HTTP_HX-Request' => 'true'],
|
||||
);
|
||||
|
||||
@@ -192,7 +197,7 @@ class BookingAdminControllerTest extends WebTestCase
|
||||
|
||||
$crawler = $this->client->request(
|
||||
'GET',
|
||||
\sprintf('/admin/spot/%d/%s', $desk->getId(), (new \DateTimeImmutable('today'))->format('Y-m-d')),
|
||||
\sprintf('/manage/spot/%d/%s', $desk->getId(), (new \DateTimeImmutable('today'))->format('Y-m-d')),
|
||||
server: ['HTTP_HX-Request' => 'true'],
|
||||
);
|
||||
|
||||
@@ -201,13 +206,90 @@ class BookingAdminControllerTest extends WebTestCase
|
||||
self::assertStringNotContainsString('Carl Conrad', $text);
|
||||
}
|
||||
|
||||
public function testTheDashboardTileIsAdminOnly(): void
|
||||
public function testTheDashboardTileIsForSupervisorsAndAdminsOnly(): void
|
||||
{
|
||||
$this->client->loginUser($this->user('[email protected]'));
|
||||
self::assertCount(0, $this->client->request('GET', '/')->filter('a[href="/admin/bookings"]'));
|
||||
self::assertCount(0, $this->client->request('GET', '/')->filter('a[href="/manage/bookings"]'));
|
||||
|
||||
$this->client->loginUser($this->supervisor());
|
||||
$crawler = $this->client->request('GET', '/');
|
||||
self::assertCount(1, $crawler->filter('a[href="/manage/bookings"]'));
|
||||
// The tile beside it leads into /admin, which a supervisor cannot enter.
|
||||
self::assertCount(0, $crawler->filter('a[href="/admin/resources"]'));
|
||||
|
||||
$this->client->loginUser($this->admin());
|
||||
self::assertCount(1, $this->client->request('GET', '/')->filter('a[href="/admin/bookings"]'));
|
||||
self::assertCount(1, $this->client->request('GET', '/')->filter('a[href="/manage/bookings"]'));
|
||||
}
|
||||
|
||||
public function testASupervisorSeesTheOverview(): void
|
||||
{
|
||||
$this->booking($this->user('[email protected]', 'Bea Berger'), $this->resource('D1'));
|
||||
$this->client->loginUser($this->supervisor());
|
||||
|
||||
$crawler = $this->client->request('GET', '/manage/bookings');
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertStringContainsString('Bea Berger', $crawler->filter('body')->text());
|
||||
}
|
||||
|
||||
public function testASupervisorReleasesABookingOnBehalfOfSomebodyElse(): void
|
||||
{
|
||||
$booking = $this->booking($this->user('[email protected]', 'Bea Berger'), $this->resource('D1'));
|
||||
$id = $booking->getId();
|
||||
$this->client->loginUser($this->supervisor());
|
||||
|
||||
$uri = \sprintf('/bookings/%d/release?return=manage', $id);
|
||||
$this->client->request('POST', $uri, ['_token' => $this->confirmationToken($uri)], server: ['HTTP_HX-Request' => 'true']);
|
||||
|
||||
self::assertSame('/manage/bookings', $this->client->getResponse()->headers->get('HX-Redirect'));
|
||||
$this->em->clear();
|
||||
self::assertNull($this->em->getRepository(Booking::class)->find($id));
|
||||
}
|
||||
|
||||
public function testASupervisorEndsSomebodyElsesSeries(): void
|
||||
{
|
||||
$series = new BookingSeries(
|
||||
$this->user('[email protected]', 'Bea Berger'),
|
||||
$this->resource('D1'),
|
||||
RecurrenceRule::DAILY,
|
||||
new \DateTimeImmutable('+1 day'),
|
||||
new \DateTimeImmutable('+2 days'),
|
||||
TimeRange::wholeDay(),
|
||||
);
|
||||
$this->em->persist($series);
|
||||
$this->em->flush();
|
||||
$this->client->loginUser($this->supervisor());
|
||||
|
||||
$uri = \sprintf('/series/%d/end?return=manage', $series->getId());
|
||||
$this->client->request('POST', $uri, ['_token' => $this->confirmationToken($uri)], server: ['HTTP_HX-Request' => 'true']);
|
||||
|
||||
self::assertSame('/manage/bookings', $this->client->getResponse()->headers->get('HX-Redirect'));
|
||||
}
|
||||
|
||||
public function testAnOccupiedDeskIsClickableForASupervisor(): void
|
||||
{
|
||||
$desk = $this->resource('D1');
|
||||
$this->booking($this->user('[email protected]', 'Bea Berger'), $desk);
|
||||
$date = (new \DateTimeImmutable('today'))->format('Y-m-d');
|
||||
$this->client->loginUser($this->supervisor());
|
||||
|
||||
$crawler = $this->client->request('GET', '/book/desk?date='.$date);
|
||||
|
||||
self::assertCount(1, $crawler->filter(\sprintf('[data-spot][hx-get="/manage/spot/%d/%s"]', $desk->getId(), $date)));
|
||||
}
|
||||
|
||||
/**
|
||||
* A supervisor frees bookings; configuring what can be booked and who reads which code stays
|
||||
* with the administrators.
|
||||
*/
|
||||
public function testASupervisorCannotEnterTheAdminArea(): void
|
||||
{
|
||||
$this->client->loginUser($this->supervisor());
|
||||
|
||||
foreach (['/admin/resources', '/admin/access-codes'] as $path) {
|
||||
$this->client->request('GET', $path);
|
||||
self::assertSame(Response::HTTP_FORBIDDEN, $this->client->getResponse()->getStatusCode(), $path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -222,16 +304,16 @@ class BookingAdminControllerTest extends WebTestCase
|
||||
|
||||
$crawler = $this->client->request(
|
||||
'GET',
|
||||
\sprintf('/bookings/%d/release?return=admin', $booking->getId()),
|
||||
\sprintf('/bookings/%d/release?return=manage', $booking->getId()),
|
||||
server: ['HTTP_HX-Request' => 'true'],
|
||||
);
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
$modal = $crawler->filter('#htmx-confirmation-modal');
|
||||
self::assertStringContainsString('Bea Berger', $modal->text());
|
||||
// Posts back to the same URI, which is what carries ?return=admin through the round trip.
|
||||
// Posts back to the same URI, which is what carries ?return=manage through the round trip.
|
||||
self::assertSame(
|
||||
\sprintf('https://localhost/bookings/%d/release?return=admin', $booking->getId()),
|
||||
\sprintf('https://localhost/bookings/%d/release?return=manage', $booking->getId()),
|
||||
$modal->filter('[hx-post]')->attr('hx-post'),
|
||||
);
|
||||
}
|
||||
@@ -250,12 +332,12 @@ class BookingAdminControllerTest extends WebTestCase
|
||||
}
|
||||
$this->client->loginUser($this->admin());
|
||||
|
||||
$crawler = $this->client->request('GET', '/admin/bookings');
|
||||
$crawler = $this->client->request('GET', '/manage/bookings');
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertCount(25, $crawler->filter('tbody tr'));
|
||||
|
||||
$crawler = $this->client->request('GET', '/admin/bookings?page=2');
|
||||
$crawler = $this->client->request('GET', '/manage/bookings?page=2');
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertCount(5, $crawler->filter('tbody tr'));
|
||||
@@ -272,7 +354,7 @@ class BookingAdminControllerTest extends WebTestCase
|
||||
$this->booking($this->user('[email protected]', 'Carl Conrad'), $this->resource('D2'));
|
||||
$this->client->loginUser($this->admin());
|
||||
|
||||
$crawler = $this->client->request('GET', '/admin/bookings?user='.$bea->getId().'&sort=u.name&direction=desc');
|
||||
$crawler = $this->client->request('GET', '/manage/bookings?user='.$bea->getId().'&sort=u.name&direction=desc');
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
$text = $crawler->filter('tbody')->text();
|
||||
@@ -299,6 +381,11 @@ class BookingAdminControllerTest extends WebTestCase
|
||||
return $booking;
|
||||
}
|
||||
|
||||
private function supervisor(string $email = '[email protected]', string $name = 'Sam Supervisor'): User
|
||||
{
|
||||
return $this->user($email, $name, ['ROLE_EMPLOYEE', 'ROLE_SUPERVISOR']);
|
||||
}
|
||||
|
||||
private function admin(string $email = '[email protected]', string $name = 'Chef Chefin'): User
|
||||
{
|
||||
return $this->user($email, $name, ['ROLE_ADMIN']);
|
||||
Reference in new issue
Block a user