feat(db) : documenter toutes les colonnes BDD via COMMENT ON COLUMN + garde-fou (ERP-67)
- Migration retrofit Version20260528120000 : pose COMMENT ON TABLE/COLUMN sur les 11 tables metier existantes (53 colonnes) via le catalogue partage ColumnCommentsCatalog (Shared/Infrastructure/Database). - Commande app:apply-column-comments : 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 workflow CI Gitea. - Test architecture tests/Architecture/ColumnsHaveSqlCommentTest : echoue si une colonne public n'a pas de col_description (hors doctrine_migration_versions et fake_site_aware_entity). Whitelist metier vide. - Convention documentee dans CLAUDE.md (regle ABSOLUE n°12) et .claude/rules/backend.md (section Migrations Doctrine) avec exemples et helper standardise pour les colonnes Timestampable/Blamable.
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
<?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.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user