feat(catalog) : M6 — Catalogue produits (ERP-197 → ERP-203) (#154)
Auto Tag Develop / tag (push) Successful in 11s
Auto Tag Develop / tag (push) Successful in 11s
Module **M6 — Catalogue produits** (ERP-197 → ERP-203), pile consolidée en une seule MR vers `develop` pour un CI unique. Contenu (commits) : - ERP-197 — permissions `catalog.products.*` + sidebar + 3 miroirs RBAC - ERP-198 — migration schéma M6 (storage_type, product, jonctions, type PRODUIT) - ERP-199 — entités Product + StorageType + repositories + contrat de sérialisation - ERP-200 — ProductProvider + ProductProcessor (unicité code, RG-6.03/05/06, normalisation) - ERP-201 — référentiel StorageType exposé (filtre site) + seed Figma + catégories PRODUIT - ERP-202 — export XLSX du catalogue produits (filtres liste) - ERP-203 — tests PHPUnit RG-6.01→6.10 + capture du contrat JSON produit - fix review M6 — default jsonb mort (states) + constante préfixe storage-type de test Remplace et clôt les MR #148, #149, #150, #151, #152, #153 (commits intégralement inclus ici). --------- Co-authored-by: admin malio <malio@yuno.malio.fr> Co-authored-by: Matthieu <contact@malio.fr> Reviewed-on: #154
This commit was merged in pull request #154.
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Module\Catalog\Api;
|
||||
|
||||
/**
|
||||
* RBAC du catalogue produit (M6, spec-back § 5.2 — admin-only, C7).
|
||||
*
|
||||
* La matrice est volontairement tres restrictive : seul l'Admin porte
|
||||
* `catalog.products.view` / `.manage`. Les 4 personas metier MALIO (Bureau,
|
||||
* Compta, Commerciale, Usine) n'ont AUCUNE permission produit -> 403 partout.
|
||||
*
|
||||
* On prouve aussi que l'acces n'est pas « admin only » par hasard mais bien
|
||||
* porte par les permissions : un non-admin avec `view` lit (200) mais ne peut
|
||||
* pas creer (403, refus au niveau securite avant denormalisation).
|
||||
*
|
||||
* Note : on ne teste pas « un non-admin avec `manage` cree un produit » — ce role
|
||||
* n'existe dans aucun persona (catalogue admin-only) et un tel user ne pourrait
|
||||
* de toute facon pas resoudre les IRI sites / categories / storage_types lors de
|
||||
* la denormalisation (ces ressources portent leur propre controle d'acces). La
|
||||
* creation par un porteur de `manage` est couverte par l'Admin (qui bypass).
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ProductRBACMatrixTest extends AbstractProductApiTestCase
|
||||
{
|
||||
/** Personas metier sans permission produit (§ 5.2). */
|
||||
private const array PERSONAS = ['Bureau', 'Compta', 'Commerciale', 'Usine'];
|
||||
|
||||
public function testAdminHasFullAccess(): void
|
||||
{
|
||||
$client = $this->createAdminClient();
|
||||
|
||||
$client->request('GET', '/api/products', ['headers' => ['Accept' => self::LD]]);
|
||||
self::assertResponseStatusCodeSame(200);
|
||||
|
||||
$client->request('POST', '/api/products', [
|
||||
'headers' => ['Content-Type' => self::LD],
|
||||
'json' => $this->validProductPayload(),
|
||||
]);
|
||||
self::assertResponseStatusCodeSame(201);
|
||||
}
|
||||
|
||||
public function testBusinessPersonasAreForbiddenEverywhere(): void
|
||||
{
|
||||
// Produit existant cible des operations item (seede par l'admin via l'EM).
|
||||
$product = $this->seedProductEntity();
|
||||
$id = (int) $product->getId();
|
||||
|
||||
foreach (self::PERSONAS as $persona) {
|
||||
$client = $this->createPersonaClient($persona);
|
||||
|
||||
$client->request('GET', '/api/products', ['headers' => ['Accept' => self::LD]]);
|
||||
self::assertResponseStatusCodeSame(403, $persona.' ne doit pas lister les produits.');
|
||||
|
||||
$client->request('GET', '/api/products/'.$id, ['headers' => ['Accept' => self::LD]]);
|
||||
self::assertResponseStatusCodeSame(403, $persona.' ne doit pas consulter un produit.');
|
||||
|
||||
$client->request('POST', '/api/products', [
|
||||
'headers' => ['Content-Type' => self::LD],
|
||||
'json' => $this->validProductPayload(),
|
||||
]);
|
||||
self::assertResponseStatusCodeSame(403, $persona.' ne doit pas creer de produit.');
|
||||
|
||||
$client->request('PATCH', '/api/products/'.$id, [
|
||||
'headers' => ['Content-Type' => self::MERGE],
|
||||
'json' => ['name' => 'Renomme par '.$persona],
|
||||
]);
|
||||
self::assertResponseStatusCodeSame(403, $persona.' ne doit pas modifier un produit.');
|
||||
}
|
||||
}
|
||||
|
||||
public function testViewPermissionReadsButCannotManage(): void
|
||||
{
|
||||
$product = $this->seedProductEntity();
|
||||
$client = $this->authView();
|
||||
|
||||
$client->request('GET', '/api/products', ['headers' => ['Accept' => self::LD]]);
|
||||
self::assertResponseStatusCodeSame(200);
|
||||
|
||||
$client->request('GET', '/api/products/'.$product->getId(), ['headers' => ['Accept' => self::LD]]);
|
||||
self::assertResponseStatusCodeSame(200);
|
||||
|
||||
// view sans manage : creation refusee au niveau securite (403 avant que la
|
||||
// denormalisation ne tente de resoudre les IRI -> pas de 400 parasite).
|
||||
$client->request('POST', '/api/products', [
|
||||
'headers' => ['Content-Type' => self::LD],
|
||||
'json' => $this->validProductPayload(),
|
||||
]);
|
||||
self::assertResponseStatusCodeSame(403);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user