/** * Tests unit pour invalidateTable — verifie les patterns generes (generique * style Notion, plus de cascade rollup metier). */ import { describe, expect, it } from 'vitest'; import { type CacheInvalidator, invalidateTable } from '../../src/lib/cache.js'; class FakeRedis implements CacheInvalidator { public patterns: string[] = []; async invalidatePattern(pattern: string): Promise { this.patterns.push(pattern); return 1; } } describe('invalidateTable', () => { it('avec rowId : invalide list + views + row precis', async () => { const redis = new FakeRedis(); await invalidateTable(redis, 42, 100); expect(redis.patterns).toContain('bridge:tables:42:list:*'); expect(redis.patterns).toContain('bridge:tables:42:views:*'); expect(redis.patterns).toContain('bridge:tables:42:row:100'); expect(redis.patterns).toHaveLength(3); }); it('sans rowId : invalide list + views uniquement', async () => { const redis = new FakeRedis(); await invalidateTable(redis, 42); expect(redis.patterns).toContain('bridge:tables:42:list:*'); expect(redis.patterns).toContain('bridge:tables:42:views:*'); expect(redis.patterns).toHaveLength(2); }); it('pas de cascade cross-table (style Notion : webhook par table)', async () => { const redis = new FakeRedis(); await invalidateTable(redis, 42, 1); // Aucun pattern d'autre table. expect(redis.patterns.every((p) => p.startsWith('bridge:tables:42:'))).toBe(true); }); it('idempotent : deux invalidations meme key', async () => { const redis = new FakeRedis(); await invalidateTable(redis, 42, 100); await expect(invalidateTable(redis, 42, 100)).resolves.toBeGreaterThanOrEqual(0); expect(redis.patterns.filter((p) => p === 'bridge:tables:42:row:100')).toHaveLength(2); }); it('retourne le total des keys invalidees', async () => { const redis = new FakeRedis(); const total = await invalidateTable(redis, 42, 1); // FakeRedis retourne 1 par appel, 3 patterns. expect(total).toBe(3); }); it('tableId 0 ou negatif : pattern construit tel quel (le caller doit valider)', async () => { const redis = new FakeRedis(); await invalidateTable(redis, 0); expect(redis.patterns).toContain('bridge:tables:0:list:*'); }); it('rowId numerique 0 inclus comme cle valide', async () => { const redis = new FakeRedis(); await invalidateTable(redis, 5, 0); expect(redis.patterns).toContain('bridge:tables:5:row:0'); }); it('plusieurs invalidations consecutives sur differentes tables sont independantes', async () => { const redis = new FakeRedis(); await invalidateTable(redis, 1, 100); await invalidateTable(redis, 2, 200); expect(redis.patterns).toContain('bridge:tables:1:row:100'); expect(redis.patterns).toContain('bridge:tables:2:row:200'); expect(redis.patterns.filter((p) => p.startsWith('bridge:tables:1:'))).toHaveLength(3); expect(redis.patterns.filter((p) => p.startsWith('bridge:tables:2:'))).toHaveLength(3); }); });