Files
Starseed/tests/Architecture/ColumnsHaveSqlCommentTest.php
T
matthieu a948eed9b6
Auto Tag Develop / tag (push) Successful in 7s
[ERP-67] Documenter toutes les colonnes BDD via COMMENT ON COLUMN + garde-fou (#24)
Ticket Lesstime : ERP-67 — `[Convention SQL / Backend / L]`

## Objectif

Documenter toutes les colonnes BDD via `COMMENT ON COLUMN` (visible dans DBeaver / DataGrip / pgAdmin sans lire le code Doctrine) et verrouiller la convention par un garde-fou de test architecture.

## Changements

### Convention (CLAUDE.md + rules)

- `CLAUDE.md` regle ABSOLUE n°12 : toute migration creant ou modifiant une colonne doit poser un `COMMENT ON COLUMN` (FR, ≤ 200 caracteres).
- `.claude/rules/backend.md` § Migrations Doctrine : exemples + helper standardise pour les 4 colonnes du `TimestampableBlamableTrait`.

### Garde-fou architecture

- `tests/Architecture/ColumnsHaveSqlCommentTest` : echoue si une colonne `public` n'a pas de `col_description` (hors `doctrine_migration_versions` et `fake_site_aware_entity` fixture de test).
- Whitelist metier `EXCLUDED_TABLES` volontairement vide.

### Retrofit des tables existantes

- Migration `Version20260528120000` : 64 `COMMENT ON TABLE/COLUMN` sur les 11 tables metier (audit_log, category, category_type, permission, role, role_permission, site, user, user_permission, user_role, user_site).
- Source unique de verite : `src/Shared/Infrastructure/Database/ColumnCommentsCatalog.php`.
- Commande `app:apply-column-comments` (Module/Core/Infrastructure/Console) : rejoue le catalogue apres `doctrine:schema:update --force` (sinon l'ORM drop les commentaires absents du mapping PHP). Branchee dans `makefile test-db-setup` et `.gitea/workflows/pull-request.yml`.

## Validation

- `make db-reset` puis `make test` : 312 tests verts, 0 regression.
- `make php-cs-fixer-allow-risky` : 0 fix.
- Couverture : 53/53 colonnes documentees sur `starseed` et `starseed_test`.

## Test plan

- [ ] `make db-reset` passe sans erreur.
- [ ] `make test` passe ; `ColumnsHaveSqlCommentTest` vert sur DB de test.
- [ ] Verifier dans DBeaver / pgAdmin que les commentaires apparaissent sur les colonnes de `category`, `user`, `audit_log`.
- [ ] Verifier que le workflow CI Gitea (`pull-request.yml`) passe.

## A noter pour la suite

La convention `options: ['comment' => '...']` sur chaque `#[ORM\Column]` reste recommandee pour les nouvelles entites — Doctrine genere alors automatiquement le `COMMENT ON COLUMN` dans la migration et `schema:update` le preserve sans avoir a rejouer le catalogue. A discuter si on veut en faire une regle forte.

---------

Co-authored-by: admin malio <malio@yuno.malio.fr>
Co-authored-by: Matthieu <contact@malio.fr>
Co-authored-by: Matthieu <mtholot19@gmail.com>
Reviewed-on: #24
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
2026-05-29 09:41:29 +00:00

115 lines
4.2 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Tests\Architecture;
use Doctrine\DBAL\ArrayParameterType;
use Doctrine\DBAL\Connection;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
/**
* Garde-fou architecture : toute colonne d'une table metier doit porter une
* description SQL (`COMMENT ON COLUMN`).
*
* Postgres stocke la description dans `pg_description`, recuperable via
* `col_description(table_oid, column_position)`. Une colonne sans description
* remonte `NULL`. Le test parcourt `information_schema.columns` filtre sur le
* schema `public` et echoue si une seule colonne metier n'a pas de description.
*
* Tables ignorees :
* - `doctrine_migration_versions` : table system Doctrine, schema fige par la
* librairie.
* - Whitelist `EXCLUDED_TABLES` : doit rester vide ou justifiee — toute entree
* doit avoir un ticket Lesstime ouvert pour le retrofit.
*
* @internal
*/
final class ColumnsHaveSqlCommentTest extends KernelTestCase
{
/**
* Tables system, gerees par Doctrine — leur schema n'est pas notre.
*/
private const EXCLUDED_BUILTINS = [
'doctrine_migration_versions',
];
/**
* Entites mappees uniquement en `when@test` (fixtures techniques pour les
* tests d'integration, jamais en prod). Pas de migration, donc pas de
* lieu naturel pour poser un COMMENT ON COLUMN.
*
* @var list<string>
*/
private const EXCLUDED_TEST_FIXTURES = [
// tests/Fixtures/SiteAware/FakeSiteAwareEntity.php — fixture du module
// Sites pour couvrir le SiteScopedQueryExtension. Cree via schema:update
// sur la DB de test uniquement.
'fake_site_aware_entity',
];
/**
* Whitelist metier — DOIT rester vide ou justifiee.
*
* Chaque entree doit comporter (1) un commentaire expliquant pourquoi la
* table n'est pas encore documentee et (2) la reference d'un ticket
* Lesstime ouvert pour le retrofit.
*
* @var list<string>
*/
private const EXCLUDED_TABLES = [];
public function testAllPublicColumnsHaveASqlComment(): void
{
/** @var Connection $conn */
$conn = self::getContainer()->get('doctrine.dbal.default_connection');
$excluded = [...self::EXCLUDED_BUILTINS, ...self::EXCLUDED_TEST_FIXTURES, ...self::EXCLUDED_TABLES];
$rows = $conn->fetchAllAssociative(
<<<'SQL'
SELECT c.table_name, c.column_name
FROM information_schema.columns c
WHERE c.table_schema = 'public'
AND c.table_name NOT IN (:excluded)
AND col_description(
(c.table_schema || '.' || c.table_name)::regclass,
c.ordinal_position
) IS NULL
ORDER BY c.table_name, c.ordinal_position
SQL,
['excluded' => $excluded],
['excluded' => ArrayParameterType::STRING],
);
if ([] !== $rows) {
$missing = array_map(
static fn (array $row): string => sprintf('%s.%s', $row['table_name'], $row['column_name']),
$rows,
);
self::fail(sprintf(
"%d colonne(s) sans COMMENT ON COLUMN — ajouter une description SQL dans la migration qui les cree (cf. .claude/rules/backend.md § Migrations Doctrine) :\n - %s",
count($missing),
implode("\n - ", $missing),
));
}
// Garde : si la requete ne renvoie rien et qu'aucune table publique
// n'existe (sauf doctrine_migration_versions), le test deviendrait un
// faux positif vert. On verifie qu'il y a bien des tables a auditer.
$tableCount = (int) $conn->fetchOne(
<<<'SQL'
SELECT COUNT(*)
FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name NOT IN (:excluded)
SQL,
['excluded' => $excluded],
['excluded' => ArrayParameterType::STRING],
);
self::assertGreaterThan(0, $tableCount, 'Aucune table publique a auditer : schema vide ou whitelist trop large.');
}
}