Skip to content

Commit c973f0e

Browse files
committed
fix: clear the admin URL generator before reusing it
Changes - Call `unsetAll()` before `setController()` in `DashboardController` and `SecurityContractCrudController` - Add `DashboardControllerTest`, and put `SecurityContractCrudController` into the admin smoke test's provider Why EasyAdmin registers `AdminUrlGenerator` as `shared: no`, so each injection point gets its own instance — but both consumers here are shared, so that instance lives as long as they do, which in a worker is longer than one request. It accumulates route parameters as it is used. `AppExtension` and `RepoAdvisoryService` already opened with `unsetAll()`; these two were the inconsistency, and inconsistency is what rots. Neither site was covered. The dashboard test asserts where the redirect lands rather than that it merely redirects, because the failure mode worth catching is silent: `unsetAll()` placed after `setController()` wipes the controller back out and produces a URL pointing somewhere else without anything throwing. Both new tests fail against that arrangement.
1 parent 85976da commit c973f0e

5 files changed

Lines changed: 79 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4040
- Implement `ResetInterface` on `LeantimeService`, whose memoised user
4141
directory has to stay a cache — `resolveUserName()` runs in a loop over
4242
tickets — but must not outlive the request
43-
- Cover all three with tests: the factories had none
43+
- Call `unsetAll()` before `setController()` on the injected
44+
`AdminUrlGenerator` in `DashboardController` and
45+
`SecurityContractCrudController`, which `AppExtension` and
46+
`RepoAdvisoryService` already did. The instance is held for as long as its
47+
consumer, which in a worker outlives the request
48+
- Cover all of it with tests: the factories had none, and neither the
49+
dashboard nor the Security Contract CRUD was in the admin smoke test
4450
- [#96](https://github.com/itk-dev/devops_itksites/pull/96)
4551
Show the Service Agreements monthly price as Danish kroner,
4652
`12.500,50 kr.`, on index and detail

src/Controller/Admin/DashboardController.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public function __construct(
2828
public function index(): Response
2929
{
3030
$d = $this->adminUrlGenerator
31+
->unsetAll()
3132
->setController(ServerCrudController::class)->setAction(Crud::PAGE_INDEX)
3233
->generateUrl();
3334

src/Controller/Admin/SecurityContractCrudController.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ public function syncAll(): RedirectResponse
132132

133133
return $this->redirect(
134134
$this->adminUrlGenerator
135+
->unsetAll()
135136
->setController(static::class)
136137
->setAction(Crud::PAGE_INDEX)
137138
->generateUrl()

tests/Controller/Admin/AdminSmokeTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use App\Controller\Admin\OIDCCrudController;
1818
use App\Controller\Admin\PackageCrudController;
1919
use App\Controller\Admin\PackageVersionCrudController;
20+
use App\Controller\Admin\SecurityContractCrudController;
2021
use App\Controller\Admin\ServerCrudController;
2122
use App\Controller\Admin\ServiceCertificateCrudController;
2223
use App\Controller\Admin\SiteCrudController;
@@ -68,6 +69,7 @@ public static function crudControllerProvider(): iterable
6869
yield 'OIDC' => [OIDCCrudController::class];
6970
yield 'Package' => [PackageCrudController::class];
7071
yield 'PackageVersion' => [PackageVersionCrudController::class];
72+
yield 'SecurityContract' => [SecurityContractCrudController::class];
7173
yield 'Server' => [ServerCrudController::class];
7274
yield 'ServiceCertificate' => [ServiceCertificateCrudController::class];
7375
yield 'Site' => [SiteCrudController::class];
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Tests\Controller\Admin;
6+
7+
use App\Controller\Admin\ServerCrudController;
8+
use App\Entity\User;
9+
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
10+
use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator;
11+
use Hautelook\AliceBundle\PhpUnit\RefreshDatabaseTrait;
12+
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
13+
14+
/**
15+
* The dashboard exists only to redirect to the Server index.
16+
*
17+
* It builds that URL on an injected AdminUrlGenerator, and the chain starts
18+
* with unsetAll() because the generator instance is held for as long as this
19+
* controller is — which, in a worker, is longer than one request. Order matters
20+
* there: unsetAll() after setController() rather than before would wipe the
21+
* controller back out and produce a URL pointing nowhere in particular, without
22+
* anything throwing. Hence asserting where the redirect actually lands.
23+
*/
24+
class DashboardControllerTest extends WebTestCase
25+
{
26+
use RefreshDatabaseTrait;
27+
28+
public function testItRedirectsToTheServerIndex(): void
29+
{
30+
$client = static::createClient();
31+
32+
$user = static::getContainer()->get('doctrine')->getManager()
33+
->getRepository(User::class)->findOneBy([]);
34+
$client->loginUser($user);
35+
36+
$client->request('GET', '/admin');
37+
38+
$this->assertResponseRedirects();
39+
40+
// Ask EasyAdmin what that URL should be rather than hard-coding the
41+
// slug, so this keeps working if the route path changes.
42+
$expected = static::getContainer()->get(AdminUrlGenerator::class)
43+
->setController(ServerCrudController::class)
44+
->setAction(Crud::PAGE_INDEX)
45+
->generateUrl();
46+
47+
$location = (string) $client->getResponse()->headers->get('Location');
48+
$this->assertSame(
49+
parse_url($expected, \PHP_URL_PATH),
50+
parse_url($location, \PHP_URL_PATH),
51+
'the redirect must still land on the Server index'
52+
);
53+
}
54+
55+
public function testTheRedirectTargetLoads(): void
56+
{
57+
$client = static::createClient();
58+
59+
$user = static::getContainer()->get('doctrine')->getManager()
60+
->getRepository(User::class)->findOneBy([]);
61+
$client->loginUser($user);
62+
63+
$client->request('GET', '/admin');
64+
$client->followRedirect();
65+
66+
$this->assertResponseIsSuccessful();
67+
}
68+
}

0 commit comments

Comments
 (0)