Exposition de Site via API Platform (5 operations RBAC sites.view/sites.manage), relation User.sites (M2M user_site EAGER) + User.currentSite (M2O nullable, ON DELETE SET NULL). Endpoint PATCH /api/me/current-site via ressource virtuelle + processor (SiteNotAuthorizedException → 403). UserRbacProcessor etendu avec gardes post-persist : auto-reset si currentSite retire, auto-select premier site si null + sites non vide. Page /admin/sites (DataTable + drawer creation/edition + modale suppression). UserRbacDrawer etendu avec section "Sites autorises". Colonne "Sites" ajoutee dans la table /admin/users (liste des noms separes par virgule). Sidebar entree Sites (module: sites, permission: sites.view). Refactor adresse : split full_address en street + complement (nullable) + getter computed Site::getFullAddress() multi-lignes. Migration ALTER dediee pour compat devs ayant deja joue le ticket 1. Fixtures avec vraies adresses (Chatellerault/Fontenet/Pommevic). Doctrine : inversedBy synchrone User.sites <-> Site.users pour maintenir la collection inverse en memoire. User::switchCurrentSite() porte la garde domaine (throw SiteNotAuthorizedException), aligne sur Role::ensureDeletable. Helper skipIfSitesModuleDisabled centralise dans AbstractApiTestCase. Tests : 182/182 (182/182 aussi module desactive, 2 skipped). 29 nouveaux tests PHPUnit (CRUD API, switch currentSite, cascade DB, /api/me enrichi, extension /rbac, gardes structurelles fullAddress/currentSite ignores, anti-cycle Site.users). 11 tests Vitest sur la validation hex couleur. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
134 lines
4.7 KiB
PHP
134 lines
4.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Module\Sites\Domain\Entity;
|
|
|
|
use App\Module\Sites\Domain\Entity\Site;
|
|
use DateTimeImmutable;
|
|
use PHPUnit\Framework\TestCase;
|
|
use ReflectionClass;
|
|
|
|
/**
|
|
* Tests unitaires de comportement de l'entite Site : etat initial, setters,
|
|
* gestion des timestamps et getter d'adresse complete. Les contraintes de
|
|
* validation (regex, unicite) sont couvertes par SiteValidationTest.
|
|
*
|
|
* @internal
|
|
*/
|
|
final class SiteTest extends TestCase
|
|
{
|
|
public function testConstructorInitialState(): void
|
|
{
|
|
$site = new Site(
|
|
name: 'Chatellerault',
|
|
street: "1 avenue de l'Europe",
|
|
complement: null,
|
|
postalCode: '86100',
|
|
city: 'Chatellerault',
|
|
color: '#056CF2',
|
|
);
|
|
|
|
self::assertNull($site->getId());
|
|
self::assertSame('Chatellerault', $site->getName());
|
|
self::assertSame("1 avenue de l'Europe", $site->getStreet());
|
|
self::assertNull($site->getComplement());
|
|
self::assertSame('86100', $site->getPostalCode());
|
|
self::assertSame('Chatellerault', $site->getCity());
|
|
self::assertSame('#056CF2', $site->getColor());
|
|
self::assertInstanceOf(DateTimeImmutable::class, $site->getCreatedAt());
|
|
self::assertInstanceOf(DateTimeImmutable::class, $site->getUpdatedAt());
|
|
}
|
|
|
|
public function testCreatedAtAndUpdatedAtAreInitiallyEqual(): void
|
|
{
|
|
$site = new Site('A', 'Rue X', null, '12345', 'B', '#000000');
|
|
|
|
// A la creation, les deux timestamps sont seedes avec la meme valeur
|
|
// pour garantir updated_at >= created_at au niveau base.
|
|
self::assertEquals($site->getCreatedAt(), $site->getUpdatedAt());
|
|
}
|
|
|
|
public function testOnPreUpdateAdvancesUpdatedAtOnly(): void
|
|
{
|
|
$site = new Site('A', 'Rue X', null, '12345', 'B', '#000000');
|
|
$originalCreatedAt = $site->getCreatedAt();
|
|
|
|
// On force updatedAt a une valeur strictement anterieure via reflection
|
|
// pour ne pas dependre d'un `sleep()` (flaky en CI, lent) : l'entite
|
|
// n'expose volontairement pas de setter sur updatedAt, c'est le
|
|
// callback Doctrine PreUpdate qui s'en charge.
|
|
$pastUpdatedAt = new DateTimeImmutable('-1 hour');
|
|
$reflection = new ReflectionClass(Site::class);
|
|
$updatedAtProperty = $reflection->getProperty('updatedAt');
|
|
$updatedAtProperty->setValue($site, $pastUpdatedAt);
|
|
|
|
$site->onPreUpdate();
|
|
|
|
self::assertSame($originalCreatedAt, $site->getCreatedAt(), 'created_at doit rester immuable apres un update.');
|
|
self::assertGreaterThan($pastUpdatedAt, $site->getUpdatedAt(), 'updated_at doit avancer apres onPreUpdate().');
|
|
}
|
|
|
|
public function testSettersMutateFields(): void
|
|
{
|
|
$site = new Site('Old', 'Old Street', null, '12345', 'OldCity', '#000000');
|
|
|
|
$site->setName('New');
|
|
$site->setStreet('New Street');
|
|
$site->setComplement('Bat A');
|
|
$site->setPostalCode('67890');
|
|
$site->setCity('NewCity');
|
|
$site->setColor('#ABCDEF');
|
|
|
|
self::assertSame('New', $site->getName());
|
|
self::assertSame('New Street', $site->getStreet());
|
|
self::assertSame('Bat A', $site->getComplement());
|
|
self::assertSame('67890', $site->getPostalCode());
|
|
self::assertSame('NewCity', $site->getCity());
|
|
self::assertSame('#ABCDEF', $site->getColor());
|
|
}
|
|
|
|
public function testFullAddressGetterWithoutComplement(): void
|
|
{
|
|
$site = new Site(
|
|
name: 'Site1',
|
|
street: '1 avenue de l\'Europe',
|
|
complement: null,
|
|
postalCode: '86100',
|
|
city: 'Chatellerault',
|
|
color: '#000000',
|
|
);
|
|
|
|
self::assertSame(
|
|
"1 avenue de l'Europe\n86100 Chatellerault",
|
|
$site->getFullAddress(),
|
|
);
|
|
}
|
|
|
|
public function testFullAddressGetterWithComplement(): void
|
|
{
|
|
$site = new Site(
|
|
name: 'Site2',
|
|
street: '12 route de Poitiers',
|
|
complement: 'Batiment B',
|
|
postalCode: '86330',
|
|
city: 'Saint-Jean-de-Sauves',
|
|
color: '#000000',
|
|
);
|
|
|
|
self::assertSame(
|
|
"12 route de Poitiers\nBatiment B\n86330 Saint-Jean-de-Sauves",
|
|
$site->getFullAddress(),
|
|
);
|
|
}
|
|
|
|
public function testFullAddressGetterIgnoresEmptyComplement(): void
|
|
{
|
|
// Garde defensive : un complement vide ou whitespace-only ne doit
|
|
// pas creer une ligne vide visuellement disgracieuse.
|
|
$site = new Site('S', 'Rue', ' ', '12345', 'Ville', '#000000');
|
|
|
|
self::assertSame("Rue\n12345 Ville", $site->getFullAddress());
|
|
}
|
|
}
|