import { describe, it } from 'node:test'; import assert from 'node:assert'; import { sanitizeForRole, FORBIDDEN_CLIENT_FIELDS, applyVisibilityScope } from './creatives-visibility'; describe('Creatives Visibility Scope & Serializer', () => { const sampleCreative = { id: '11111111-1111-1111-1111-111111111111', tenant_id: '8bec977a-6032-4759-aa7f-b5042081f450', campaign_name: 'Campaña Matrículas', format: 'estatico', angle: 'prueba_social', hook: 'Aprende inglés en 3 meses', primary_text: 'El método que funciona para profesionales.', headline: 'Clases online con nativos', cta: 'Saber más', asset_url: 'https://cdn.ejemplo.com/ad1.jpg', thumbnail_url: 'https://cdn.ejemplo.com/thumb1.jpg', status: 'publicada', visibility: 'client', parent_id: '00000000-0000-0000-0000-000000000000', generation_request_id: '22222222-2222-2222-2222-222222222222', created_by: 'claude', created_at: '2026-10-12T10:00:00Z', updated_at: '2026-10-12T10:00:00Z', }; const sampleMetrics = { id: '33333333-3333-3333-3333-333333333333', tenant_id: '8bec977a-6032-4759-aa7f-b5042081f450', creative_id: '11111111-1111-1111-1111-111111111111', meta_ad_id: 'meta_123', date: '2026-10-12', impressions: 5000, clicks: 120, spend: 45.5, raw: { meta_response_debug: true, internal_details: 'sensitive' }, }; const sampleGenerationRequest = { id: '44444444-4444-4444-4444-444444444444', tenant_id: '8bec977a-6032-4759-aa7f-b5042081f450', reason: 'fatiga', brief: { client_slug: 'bps', variants_requested: 2 }, cost_estimate: { tokens: 12000, credits: 1 }, }; it('Agency role receives all fields unmodified', () => { const res = sanitizeForRole(sampleCreative, 'agency'); assert.strictEqual(res.angle, 'prueba_social'); assert.strictEqual(res.created_by, 'claude'); assert.strictEqual(res.parent_id, '00000000-0000-0000-0000-000000000000'); assert.strictEqual(res.generation_request_id, '22222222-2222-2222-2222-222222222222'); const metricsRes = sanitizeForRole(sampleMetrics, 'admin'); assert.ok(metricsRes.raw); }); it('Client role NEVER receives forbidden fields in creatives', () => { const res = sanitizeForRole(sampleCreative, 'client'); for (const field of FORBIDDEN_CLIENT_FIELDS) { assert.strictEqual( (res as any)[field], undefined, `Field "${field}" must not be present for client` ); } // Permitted fields remain assert.strictEqual(res.headline, 'Clases online con nativos'); assert.strictEqual(res.hook, 'Aprende inglés en 3 meses'); assert.strictEqual(res.primary_text, 'El método que funciona para profesionales.'); assert.strictEqual(res.status, 'publicada'); }); it('Client role NEVER receives raw in metrics', () => { const res = sanitizeForRole(sampleMetrics, 'client'); assert.strictEqual(res.raw, undefined); assert.strictEqual(res.impressions, 5000); assert.strictEqual(res.clicks, 120); }); it('Client role NEVER receives brief or cost_estimate in generation requests', () => { const res = sanitizeForRole(sampleGenerationRequest, 'client'); assert.strictEqual((res as any).brief, undefined); assert.strictEqual((res as any).cost_estimate, undefined); assert.strictEqual(res.reason, 'fatiga'); }); it('Sanitizes arrays of objects properly for client role', () => { const list = [sampleCreative, sampleCreative]; const sanitizedList = sanitizeForRole(list, 'client'); assert.strictEqual(sanitizedList.length, 2); sanitizedList.forEach((item: any) => { assert.strictEqual(item.angle, undefined); assert.strictEqual(item.created_by, undefined); assert.strictEqual(item.hook, 'Aprende inglés en 3 meses'); }); }); it('Query builder applies strict client filters for creatives', () => { const mockQuery = { filters: [] as string[], eq(k: string, v: any) { this.filters.push(`${k}=${v}`); return this; }, in(k: string, values: any[]) { this.filters.push(`${k} IN (${values.join(',')})`); return this; }, }; applyVisibilityScope(mockQuery, 'creatives', 'client', '8bec977a-6032-4759-aa7f-b5042081f450'); assert.ok(mockQuery.filters.includes('tenant_id=8bec977a-6032-4759-aa7f-b5042081f450')); assert.ok(mockQuery.filters.includes('visibility=client')); assert.ok(mockQuery.filters.some((f) => f.startsWith('status IN'))); }); });