pgsql-test: Pruebas reales de Postgres para ciclos de desarrollo más rápidos
El código de las aplicaciones dispone de ciclos de pruebas ágiles: runners, fixtures y un feedback red-green en JavaScript, TypeScript y Python. Con Postgres también es posible realizar pruebas, pero la lógica de la base de datos suele quedar fuera de estos ciclos. Los desarrolladores deben preparar el estado de la base de datos, gestionar las transacciones o terminar recurriendo a pegar una consulta, actualizar el navegador o realizar una comprobación manual. Esta brecha influye en la arquitectura. Si una regla resulta más fácil de probar en el código de la aplicación que directamente en Postgres, normalmente acaba implementándose allí, incluso cuando Postgres sería el lugar más adecuado para garantizar su cumplimiento.
Los mocks no solucionan este problema. Permiten comprobar cómo el código de la aplicación responde a un resultado, pero no si Postgres realmente generará ese resultado. Un mock no pone a prueba una clave foránea, no ejecuta un trigger, no evalúa una política de seguridad a nivel de registro ni comprueba el rol de base de datos con el que se ejecuta realmente una consulta.
pgsql-test es un harness con licencia MIT que incorpora una base de datos PostgreSQL real en este ciclo de pruebas. Crea una base de datos PostgreSQL efímera, la prepara una sola vez y, después de cada prueba, la restaura al estado inicial. Las aserciones se ejecutan con el entorno de pruebas que ya utiliza el proyecto, mientras PostgreSQL se encarga de ejecutar las restricciones, funciones y políticas que se quieren validar. No es la primera solución para probar PostgreSQL: pgTAP lleva años haciéndolo directamente con SQL. La diferencia es que pgsql-test se enfoca en la capa de aplicación, que es donde ya trabajan la mayoría de los desarrolladores.
Pruebas de seguridad a nivel de registro
La seguridad a nivel de registro (RLS) permite que Postgres aplique directamente las reglas de acceso. Las políticas forman parte de la lógica de la base de datos y son invisibles para los mocks; una política mal configurada puede provocar la exposición de registros que deberían estar protegidos. Por eso, las pruebas deben verificar tanto los datos a los que un usuario puede acceder como aquellos a los que no debería tener acceso.
El harness de pruebas pgsql-test proporciona un cliente administrativo, pg, para realizar la configuración, y un cliente de aplicación, db, para probar las concesiones de permisos y las políticas. Como los superusuarios omiten las restricciones de RLS, las pruebas deben ejecutarse utilizando los roles y permisos de base de datos que la aplicación utiliza realmente.
Supongamos que un proyecto define app.documents con una política de control de propiedad, y que sus fixtures crean el documento 101, cuyo propietario es Alice, y el documento 202, cuyo propietario es Bob. Cada prueba se ejecuta dentro de una transacción que se revierte al finalizar, de modo que todas las pruebas parten del mismo estado inicial:
import { getConnections } from 'pgsql-test'; let db, teardown; beforeAll(async () => { // create a fresh database and deploy the project's schema ({ db, teardown } = await getConnections()); }); afterAll(() => teardown()); beforeEach(() => db.beforeEach()); afterEach(() => db.afterEach()); test('Alice sees her document and not Bobs', async () => { db.setContext({ role: 'authenticated', 'jwt.claims.user_id': 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' }); const result = await db.query( 'SELECT id FROM app.documents ORDER BY id' ); expect(result.rows).toEqual([{ id: 101 }]); });
La consulta no incluye ningún filtro por propietario. La política debe permitir que el documento de Alice sea visible y excluir el de Bob de los resultados.
setContext() establece el rol y la identidad mediante SET LOCAL y set_config(..., true), de modo que esta configuración queda limitada a la transacción actual. Estos valores proporcionan la identidad que utilizan las políticas. No se realiza ninguna validación de tokens. Las identidades utilizadas en las pruebas deben coincidir con las que esperan las políticas de la aplicación. El tutorial de RLS explica cómo realizar esta configuración.
El harness inicializa los datos mediante pgsql-seed, que permite cargar archivos SQL, fixtures definidos mediante código, archivos CSV y JSON, o migraciones.
Un solo harness, múltiples plataformas
El mismo harness puede ejecutarse sobre diferentes stacks:
supabase-testproporciona los roles, esquemas y valores de configuración de autenticación predeterminados de Supabase.drizzle-orm-testpermite ejecutar consultas de Drizzle dentro de la transacción administrada.pglite-testutiliza PGlite directamente en el proceso, sin requerir un servicio de base de datos externo, siempre que el esquema y las extensiones necesarias sean compatibles.graphile-testejecuta consultas GraphQL sobre un esquema de PostGraphile conectado a la base de datos de pruebas.graphile-realtime-testincorpora además pruebas para las suscripciones.
En una de las ejecuciones de supabase-test, mencionada por Paul Copplestone, CEO de Supabase, se ejecutaron con éxito 246 pruebas en 44 bases de datos en tan solo cuatro segundos.
Preparando el ciclo de trabajo con pgpm
pgpm, el gestor de paquetes de Constructive para PostgreSQL modular, prepara los espacios de trabajo con pgsql-test, Jest y GitHub Actions; por defecto, getConnections() despliega el plan del módulo. Para empezar, ejecuta pgpm init workspace y después añade un cambio de esquema junto con una prueba.
Cada cambio incorpora scripts para desplegarlo (deploy), verificarlo (verify) y revertirlo (revert). verify comprueba que el cambio de esquema se haya aplicado correctamente, mientras que las pruebas verifican que su comportamiento sea el esperado.
El espacio de trabajo con scaffolding lleva este mismo ciclo de retroalimentación al entorno de integración continua (CI) mediante safegres. Las pruebas se encargan de comprobar que el comportamiento de la base de datos sea correcto, mientras que safegres analiza el esquema desplegado para detectar regresiones de seguridad o rendimiento. El trabajo de CI establece un umbral de seguridad y compara los problemas de rendimiento detectados con una línea base almacenada en el repositorio. Así, ningún cambio puede empeorar la calificación de seguridad del esquema ni introducir nuevos problemas de rendimiento.
Postgres aplica las reglas. pgsql-test lleva la verificación de esas reglas al ciclo de desarrollo de la aplicación.
Disponibilidad
pgsql-test y sus integraciones se distribuyen bajo la licencia MIT y están disponibles en npm y PyPI.
Para comenzar: Tutoriales · Curso de pruebas end-to-end · npm · Código fuente

