feat(realtime): Fase 2b.0 infra + first R-series tests green
Enable postgres_changes broadcasts on shopping_items / shopping_lists and prove the path end-to-end with Vitest Realtime tests and a pgTAP configuration check. Infra changes that were silently blocking events - Migration 007: shopping_items + shopping_lists added to supabase_realtime publication with REPLICA IDENTITY FULL (UPDATE/DELETE payloads need the full row so list_id-based filters work and RLS can evaluate DELETE against OLD) - Realtime service: DB_USER=supabase_admin (superuser; supabase_replication_admin lacks CREATE on the `realtime` schema that the service auto-creates per tenant) - Realtime service: SELF_HOST_TENANT_NAME=realtime so the seed tenant name matches the default supabase-js resolves for localhost URLs (was realtime-dev → TenantNotFound) Tests - pgTAP 004_realtime_publication.sql — P-01..P-04: publication membership and REPLICA IDENTITY FULL for both shopping tables - Vitest realtime-postgres-changes.test.ts — R-01 INSERT broadcast, R-02 UPDATE carries full row, R-03 list_id filter isolates events - test-utils realtime-helpers.ts — subscribePostgresChanges() returns a waitFor(predicate, timeout) helper that captures the SUBSCRIBED handshake before returning so mutations issued right after are not lost - createClientAs now calls realtime.setAuth(token) so the server evaluates RLS against the test user's JWT - Justfile test-db now globs supabase/tests/*.sql so new pgTAP files are picked up automatically Totals: 54→57 Vitest, 12→16 pgTAP, 15 Playwright = 88 tests green. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
### Fase 2b — Sincronización en Tiempo Real y Modo Compra
|
||||
**Estado: ⏳ Pendiente**
|
||||
**Estado: 🚧 En curso — tests 2b.0 verdes (Realtime infra lista)**
|
||||
**Duración estimada: 2–3 semanas**
|
||||
**Objetivo: el diferencial del producto. Es la fase más compleja técnicamente.**
|
||||
|
||||
@@ -7,11 +7,16 @@ Esta fase sigue TDD: **primero los tests, al final la verificación.** Ninguna t
|
||||
|
||||
#### 2b.0 Tests primero — escribir antes de implementar
|
||||
|
||||
**Prep infra (previa a los tests):**
|
||||
|
||||
- [x] Migración `007_realtime_publication.sql` — añade `shopping_items` y `shopping_lists` a la publicación `supabase_realtime`; pone `REPLICA IDENTITY FULL` en ambas
|
||||
- [x] Docker Compose: `DB_USER: supabase_admin` (superuser) + `SELF_HOST_TENANT_NAME: realtime` para Realtime (ver memoria `project_realtime_config`)
|
||||
|
||||
**Vitest (`packages/test-utils/tests/`):**
|
||||
|
||||
- [x] `realtime-postgres-changes.test.ts` — R-01 INSERT broadcast, R-02 UPDATE con fila completa, R-03 filtro `list_id` aísla eventos de otras listas ✅
|
||||
- [ ] `realtime-presence.test.ts` — dos clientes (Ana + Borja) entran en la misma lista; ambos aparecen en el canal Presence; salir del canal elimina al usuario
|
||||
- [ ] `realtime-postgres-changes.test.ts` — Ana hace INSERT/UPDATE/DELETE en `shopping_items`; Borja (suscrito al canal) recibe el evento en < 1s
|
||||
- [ ] `realtime-isolation.test.ts` — Ana y David (guest del mismo colectivo) están suscritos a la misma lista; Eva (fuera del colectivo) NO recibe nada aunque intente suscribirse (RLS en `supabase_realtime.broadcast`)
|
||||
- [ ] `realtime-isolation.test.ts` — Ana y David (guest del mismo colectivo) reciben eventos; Eva (fuera del colectivo) NO recibe nada (RLS)
|
||||
- [ ] `sync-queue.test.ts` (unit, con mock de `idb`) — cada mutación escribe en `pending_ops` antes de llamar a Supabase; al recibir la confirmación se elimina de la cola; si falla se mantiene para reintento
|
||||
- [ ] `sync-flush.test.ts` (unit) — al disparar el evento `online` se flushea la cola respetando el orden original; colisiones resueltas con last-write-wins
|
||||
|
||||
@@ -20,14 +25,12 @@ Esta fase sigue TDD: **primero los tests, al final la verificación.** Ninguna t
|
||||
- [ ] `session.test.ts` — S-01: abrir Modo Compra en `/lists/[id]/session` muestra el layout full-screen sin sidebar
|
||||
- [ ] `session.test.ts` — S-02: marcar un ítem lo mueve a la sección CHECKED con animación (no se duplica)
|
||||
- [ ] `session.test.ts` — S-03: botón "Finish shopping" → confirmación modal → lista pasa a `completed`, redirige a `/lists`
|
||||
- [ ] `realtime.test.ts` — R-01 (dos contextos de navegador): Ana crea ítem, Borja lo ve sin refrescar la página
|
||||
- [ ] `realtime.test.ts` — R-02: Ana marca ítem, avatar de Borja aparece en el indicador de Presence
|
||||
- [ ] `offline.test.ts` — O-01: con `context.setOffline(true)`, añadir/marcar ítems sigue funcionando localmente y aparece el banner "sin conexión"
|
||||
- [ ] `offline.test.ts` — O-02: al volver online (`setOffline(false)`), los cambios pendientes se sincronizan y el banner desaparece
|
||||
- [ ] `realtime.test.ts` — dos contextos de navegador: Ana crea ítem, Borja lo ve sin refrescar; Ana marca ítem, el avatar de Borja aparece en Presence
|
||||
- [ ] `offline.test.ts` — O-01 con `context.setOffline(true)`: mutaciones locales + banner "sin conexión"; O-02 al volver online: sync pendiente + banner desaparece
|
||||
|
||||
**pgTAP (`supabase/tests/`):**
|
||||
|
||||
- [ ] `004_realtime_rls.sql` — verificar que `realtime.messages` respeta las políticas RLS heredadas de `shopping_items` (un miembro sólo ve cambios de sus colectivos)
|
||||
- [x] `004_realtime_publication.sql` — P-01..P-04: publication + REPLICA IDENTITY FULL asegurados ✅
|
||||
|
||||
#### 2b.1 Sincronización Realtime
|
||||
|
||||
|
||||
Reference in New Issue
Block a user