Compare commits

..

7 Commits

Author SHA1 Message Date
gitea-actions 8e31e1759c chore: bump version to v0.1.48
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Successful in 18s
2026-05-28 10:25:11 +00:00
matthieu 4824690923 [ERP-48] Écrire les tests PHPUnit RG-1.01 à RG-1.17 (#20)
Auto Tag Develop / tag (push) Successful in 9s
## Mode stacked PR — DERNIER ticket back du M0

**Cible : `feature/ERP-47-declarer-module-catalog-rbac`** (PAS develop).
Quand la MR ERP-47 sera mergée sur develop, repointer la cible de cette MR vers develop.

## Résumé

Suite PHPUnit complète qui mappe chaque RG (1.01 → 1.17) de la spec M0 Catalog vers un ou plusieurs tests ciblés. **63 nouveaux tests** dans 9 classes sous `tests/Module/Catalog/Api/`.

## Cahier de test

| RG | Test(s) |
|---|---|
| **RG-1.01** | `CategoryPermissionsTest::testPersonaWithoutCatalogPermissionGets403*` (4 personas × 4 verbes) + `testAnonymousGets401*` + `testAdminGets{200,201,204}*` + `testUserWithViewPermissionGets200*` |
| **RG-1.02** | `CategoryValidationTest::testNameRequiredReturns422` + `testNameEmptyStringReturns422` + `testNameWhitespaceOnlyReturns422` |
| **RG-1.03** | `CategoryValidationTest::testNameIsTrimmedOnCreate` |
| **RG-1.04** | `CategoryValidationTest::testNameTooShortReturns422` + `testNameTooLongReturns422` + `testNameAtMaxLengthIs201` |
| **RG-1.05** | `CategoryValidationTest::testCategoryTypeRequiredReturns422` + `testCategoryTypeNullIsRejected` |
| **RG-1.06** | `CategoryValidationTest::testCategoryTypeMustExistReturns4xx` |
| **RG-1.07** | `CategoryUniqueTest::testDuplicateNameSameTypeReturns409` + `testDuplicateNameCaseInsensitiveReturns409` + `testSameNameDifferentTypeAllowed` + `testRecreateAfterSoftDeleteAllowed` |
| **RG-1.08** | `CategoryListTest::testListExcludesSoftDeletedByDefault` |
| **RG-1.09** | `CategoryListTest::testIncludeDeletedFlagSurfacesSoftDeleted` |
| **RG-1.10** | `CategoryListTest::testDefaultSortIsNameAsc` |
| **RG-1.11** | `CategoryGetTest::testGetSoftDeletedReturns404` + `testGetSoftDeletedWithFlagReturns200` + `testGetNonExistentReturns404` + `testGetActiveCategoryReturns200` |
| **RG-1.12** | `CategoryDeleteTest::testDeleteReturns204AndPersistsSoftDelete` |
| **RG-1.13** | `CategoryDeleteTest::testPatchCannotSetDeletedAt` |
| **RG-1.11 étendue (404 sur soft-deleted)** | `CategoryDeleteTest::testPatchOnSoftDeletedReturns404` + `testDeleteOnSoftDeletedReturns404` |
| **Audit** | `CategoryAuditTest::testAuditLogOnCreate` + `testAuditLogOnUpdate` + `testAuditLogOnSoftDelete` + `testAuditLogPerformerCarriesAuthenticatedUsername` |
| **RG-1.15** | `CategoryTimestampableBlamableTest::testCreatedByAdminOnPost` + `testCreatedByNullInConsoleContext` |
| **RG-1.16** | `CategoryTimestampableBlamableTest::testPatchUpdatesUpdatedFieldsOnly` + `testSoftDeleteAlsoUpdatesUpdatedFields` |
| **RG-1.17** | `EntitiesAreTimestampableBlamableTest::testAllBusinessEntitiesImplementBothInterfaces` (déjà livré ERP-52, reste vert avec `Category` détectée Timestampable/Blamable et `CategoryType` whitelistée) |

## Side fixes révélés par la suite

### 1. `Category.php` — `normalizer: 'trim'` sur Assert\NotBlank + Length

Avant le fix, POST `{name: "   "}` retournait **201** au lieu de **422** : le Processor trim après validation, mais NotBlank ne fait pas de trim natif. La RG-1.02 (whitespace-only → 422) combinée à la RG-1.03 (trim serveur) exige le `normalizer: 'trim'`. 1 ligne, aligne le contrat sans réordonnancer Validate/Process.

### 2. `makefile` — recréer l'index partiel `uq_category_name_type_active` après `schema:update`

`doctrine:schema:update --env=test --force` drop systématiquement l'index partiel `uq_category_name_type_active` (l'ORM ne sait pas exprimer un index fonctionnel + partiel via attribut Doctrine, donc le voit comme orphelin). Conséquence : POST doublon `(name, type)` retournait **201** au lieu de **409** (la `UniqueConstraintViolation` ne se déclenche plus). Fix : `dbal:run-sql "CREATE UNIQUE INDEX IF NOT EXISTS ..."` ajouté en fin de `test-db-setup`. Approche chirurgicale validée avec Matthieu, ne touche pas à `fake_site_aware_entity` (dépend de schema:update pour exister avant le purger fixtures:load).

## Helpers livrés

`tests/Module/Catalog/Api/AbstractCatalogApiTestCase.php` :
- Factories : `createCategory()`, `createCategoryType()`
- Auth : `createAdminClient()`, `createManageClient()`, `createViewClient()`, `createPersonaClient(string \$label)` (4 personas MALIO sans permission catalog)
- Cleanup : purge complète Category + CategoryType (aucune fixture au M0) + users/roles `test_*`

## Vérifications

-  `make php-cs-fixer-allow-risky` (auto-applied via pre-commit)
-  `make db-reset` (index partiel restauré, vérifié `\d category`)
-  `make test` → **311 tests, 1071 assertions, 0 failure, 0 risky**
  (248 existants + 63 nouveaux, dont 6 deprecations + 6 notices héritées des tests Core RBAC pré-existants — pas de régression introduite par ce ticket)

## Suite

DERNIER ticket back du M0. ERP-52, ERP-43, ERP-44, ERP-45, ERP-46, ERP-47, ERP-48 sont tous en review.
Quand la MR ERP-47 sera mergée sur develop, Matthieu repointera la cible de cette MR vers develop. Tristan reviewe la stack en série.

---------

Co-authored-by: Matthieu <mtholot19@gmail.com>
Reviewed-on: #20
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
2026-05-28 09:48:16 +00:00
matthieu fceb1e0e83 [ERP-47] Déclarer le module Catalog et synchroniser RBAC (#19)
Auto Tag Develop / tag (push) Successful in 10s
## Contexte

Ticket Lesstime #47 — M0 position 0.5. Wire le nouveau module **Catalog** dans Starseed et synchronise les 3 sources RBAC (sidebar + personas E2E + seed back). Couvre **RG-1.01** (Admin uniquement) côté infra.

Spec : [`docs/specs/M0-categories/spec-back.md` § 5.1 + § 5.3](https://gitea.malio.fr/MALIO-DEV/Starseed/src/branch/feature/M0-spec-categories/docs/specs/M0-categories/spec-back.md).

> ⚠ **Mode stacked PR** — cible `feature/ERP-46-exposer-category-type-lecture-seule`, **PAS** `develop`. Quand la MR ERP-46 sera mergée sur develop, repointer la cible de cette MR vers develop.

## Modifications (6 fichiers — règle ABSOLUE Starseed n°8 : les 3 sources RBAC bougent ENSEMBLE)

| Fichier | Rôle |
|---|---|
| `src/Module/Catalog/CatalogModule.php` (nouveau) | Déclaration du module : `ID=catalog`, `LABEL=Catalogue`, `REQUIRED=true`, 2 permissions (`view` + `manage`) |
| `config/modules.php` | Wire `CatalogModule::class` |
| `config/sidebar.php` | Item « Gestion des catégories » dans section Administration, gate sur `catalog.categories.view` |
| `frontend/i18n/locales/fr.json` | Clé `sidebar.catalog.categories` = `Gestion des catégories` |
| `frontend/tests/e2e/_fixtures/personas.ts` | `user-full` reçoit les 2 permissions + `'categories'` dans `expectedAdminLinks`. `super-admin` et `ALL_ADMIN_LINKS` étendus avec `'categories'`. `user-readonly` inchangé (Admin-only au M0 — pas de mode read-only spec'é). |
| `src/Module/Core/Infrastructure/Console/SeedE2ECommand.php` | Miroir back : `user-full` reçoit les 2 permissions |

## Décisions

- **`REQUIRED = true`** : la spec § 5.1 + le prompt user disent `true` (Category sera FK NOT NULL côté futurs modules Tiers). Le ticket Lesstime dit `false` par erreur — j'ai suivi la spec.
- **Personas E2E « Admin » = `user-full`** : pas de persona métier « Admin » explicite dans `personas.ts` (personas techniques : `super-admin` bypass, `user-full` = toutes permissions). `user-full` est l'équivalent fonctionnel.
- **`user-readonly` NON touché** : RG-1.01 dit « Admin uniquement », pas de pattern read-only spec'é au M0. À rouvrir dans un futur ticket si besoin.

## Validation

- `make php-cs-fixer-allow-risky` ✓ (0 fichier corrigé)
- `make db-reset` ✓ (sync-permissions : 11 codes en base, dont les 2 nouveaux `catalog.categories.*` vérifiés via `dbal:run-sql`)
- `make test` ✓ (248 tests, 0 régression)
- **RG-1.01 vérifiée manuellement** via curl :
  - Admin → 200 sur `GET /api/categories` et `GET /api/category_types`
  - Bob (zéro permission) → 403 sur `GET /api/categories`, `POST /api/categories`, `GET /api/category_types`
  - Anonyme → 401 sur `GET /api/categories`

---------

Co-authored-by: Matthieu <mtholot19@gmail.com>
Reviewed-on: #19
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
2026-05-28 09:45:33 +00:00
matthieu adda62c1e1 [ERP-46] Exposer le référentiel CategoryType en lecture seule (#18)
Auto Tag Develop / tag (push) Successful in 10s
## Contexte

Ticket Lesstime #46 — position 0.4 (M0 Catalog, quick win).

Expose `CategoryType` en lecture seule pour alimenter le `<MalioSelect>` du formulaire `Category` côté front. Pas d'écriture exposée au M0 (table vide à la livraison).

## Mode stacked PR

⚠ **Cible (base branch) : `feature/ERP-45-implementer-provider-processor-category`** (PAS develop).
Quand MR ERP-45 sera mergée sur develop, repointer la cible de cette MR vers develop.

## Changement

Le gros du travail (`#[ApiResource(operations: [GetCollection, Get])]`, security `is_granted('catalog.categories.view')`, groupes de sérialisation) a été livré dans le ticket ERP-44. Cette MR ajoute uniquement ce qui manquait à la spec § 4.6 :

- `order: ['label' => 'ASC']` sur l'opération `GetCollection` → tri alphabétique stable pour le select front.

## Critères d'acceptation (spec § 4.6)

- [x] `GET /api/category_types` retourne tous les `CategoryType` triés par `label ASC`
- [x] `GET /api/category_types/{id}` retourne le détail
- [x] POST / PATCH / DELETE → 404 (opérations non déclarées)
- [x] Security `is_granted('catalog.categories.view')` sur les 2 opérations
- [x] `make php-cs-fixer-allow-risky` passe (0 fix)

## Vérifications

\`\`\`
$ php bin/console debug:router | grep category_type
_api_/category_types{._format}_get_collection   GET   /api/category_types.{_format}
_api_/category_types/{id}{._format}_get         GET   /api/category_types/{id}.{_format}
\`\`\`

→ Exactement 2 routes générées, aucune POST/PATCH/DELETE.

- \`make php-cs-fixer-allow-risky\` ✓ (0 fix)
- \`make test\` ✓ (248/248)

---------

Co-authored-by: Matthieu <mtholot19@gmail.com>
Reviewed-on: #18
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
2026-05-28 09:45:06 +00:00
matthieu 80fabcae91 [ERP-45] Implémenter Provider et Processor Category (#17)
Auto Tag Develop / tag (push) Successful in 7s
## Mode stacked PR — cible `feature/ERP-44-creer-entites-category`

> ⚠ Cette MR a pour base la branche ERP-44 (en review). Quand ERP-44 sera mergée sur develop, **repointer la cible vers develop**.

## Résumé
- Provider `CategoryProvider` : filtre soft-delete par défaut (RG-1.08), `?includeDeleted=true` (RG-1.09), tri name ASC (RG-1.10), 404 si soft-deleted hors flag (RG-1.11).
- Processor `CategoryProcessor` : trim du `name` (RG-1.03), conversion DELETE → UPDATE (RG-1.12), mapping `UniqueConstraintViolationException` → HTTP 409 avec message exact (RG-1.07).
- Câblage Provider/Processor dans `#[ApiResource]` de `Category`. Provider câblé aussi sur Patch + Delete (au-delà du scope strict du ticket) pour fermer la fuite RG-1.11 sur PATCH.
- `DoctrineCategoryRepository` expose `createListQueryBuilder($includeDeleted)`.

## Décisions notables
- **Filtre soft-delete via QueryBuilder** (choix `a` du ticket) : pas de filtre Doctrine global, lisibilité directe.
- **Pas de `remove_processor` injecté** : la DELETE est convertie en UPDATE via le `persist_processor`. API Platform 4 utilise le processor déclaré sur l'opération sans fallback automatique.
- **Provider sur Patch + Delete aussi** : décision prise pendant le dev pour fermer une fuite RG-1.11 sur PATCH. Coût : 2 lignes dans `Category.php`.

## Tests
- `make php-cs-fixer-allow-risky` ✓
- `make test` ✓ (248 tests, 0 régression — pas de test métier Category, c'est ERP-48)
- Tests manuels curl ✓ — 8 cas RG-1.03 → RG-1.13 validés (détail dans le ticket Lesstime #45)

## Tickets
- Lesstime : #45 (ERP-45) → En review
- Position M0 : 0.3
- Spec : `docs/specs/M0-categories/spec-back.md` § 4.1 + § 4.3 + § 4.4 + § 4.5

## Suite
- ERP-46 (0.4 CategoryType lecture seule) — base : cette branche

---------

Co-authored-by: Matthieu <mtholot19@gmail.com>
Reviewed-on: #17
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
2026-05-28 09:44:43 +00:00
matthieu ff6086bc4d [ERP-44] Créer les entités Category et CategoryType (#15)
Auto Tag Develop / tag (push) Successful in 7s
## Objectif
Couche Domain DDD du module Catalog (ticket M0 · position 0.2). Crée les entités `Category` et `CategoryType`, leurs repositories, et branche le pattern Timestampable + Blamable Shared.

> **Mode stacked PR** : cible `feature/ERP-43-migrer-tables-category`. Quand la MR ERP-43 sera mergée sur develop, Matthieu repointera la cible de cette MR vers develop.

## Contenu
- **`Category`** : `#[ApiResource]` (GetCollection, Get, Post, Patch, Delete), `#[Auditable]`, `TimestampableBlamableTrait` + interfaces, asserts (`NotBlank`/`Length` sur `name`, `NotNull` sur `categoryType`), soft delete via `deletedAt`. Provider/Processor branchés au ticket 0.3 (ERP-45).
- **`CategoryType`** : référentiel statique en lecture seule (GetCollection + Get), embarqué dans `Category` via le groupe `category:read`. Pas de Trait — whitelisté dans `EntitiesAreTimestampableBlamableTest::EXCLUDED` (RG-1.17).
- **Repositories** : interfaces Domain + implémentations Doctrine.
- **`config/packages/doctrine.yaml`** : mapping ORM `Catalog` inconditionnel (miroir de `Sites`) — nécessaire pour que l'ORM reconnaisse les entités. La déclaration du module (`config/modules.php`) reste pour le ticket 0.5 (ERP-47).
- Groupes : `category:read` / `category:write` + `default:read` (expose les 4 colonnes du Trait).

## Notes techniques
- Index nommés déclarés sur les entités pour matcher la migration (cf. Role/Permission/Site).
- L'index unique partiel `uq_category_name_type_active` (`LOWER(name), category_type_id WHERE deleted_at IS NULL`) reste possédé par la seule migration : Doctrine ORM ne sait pas exprimer un index fonctionnel + partiel. Seul diff résiduel de `doctrine:schema:validate`.

## Tests
- `make php-cs-fixer-allow-risky` ✓
- `make test` ✓ (248 tests, 0 échec)
- `make db-reset` ✓
- `debug:router` ✓ (7 routes exposées)
- `doctrine:schema:validate` : mapping correct

---------

Co-authored-by: Matthieu <mtholot19@gmail.com>
Reviewed-on: #15
Reviewed-by: Autin <tristan@yuno.malio.fr>
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
2026-05-28 09:44:18 +00:00
gitea-actions d01bbfbc65 chore: bump version to v0.1.43
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Successful in 17s
2026-05-28 09:42:08 +00:00
+1 -1
View File
@@ -1,2 +1,2 @@
parameters:
app.version: '0.1.42'
app.version: '0.1.48'