import { describe, it, beforeEach, afterEach, expect } from 'bun:test'; import Database from 'bun:sqlite'; import { initializeDatabase } from '../../../src/db'; import { GroupSyncService } from '../../../src/services/group-sync'; import { AllowedGroups } from '../../../src/services/allowed-groups'; const envBackup = { ...process.env } as NodeJS.ProcessEnv; describe('GroupSyncService - onboarding A3', () => { let memdb: Database; beforeEach(() => { process.env = { ...envBackup, NODE_ENV: 'test', ONBOARDING_ENABLE_IN_TEST: 'true', ONBOARDING_PROMPTS_ENABLED: 'true', ONBOARDING_GRACE_SECONDS: '0', ONBOARDING_COOLDOWN_DAYS: '7', ONBOARDING_COVERAGE_THRESHOLD: '1', CHATBOT_PHONE_NUMBER: '555111222' }; memdb = new Database(':memory:'); initializeDatabase(memdb); (GroupSyncService as any).dbInstance = memdb; (AllowedGroups as any).dbInstance = memdb; // Sembrar grupo activo memdb.prepare(`INSERT INTO groups (id, community_id, name, active, last_verified) VALUES (?,?,?,?, strftime('%Y-%m-%d %H:%M:%f','now'))`) .run('g1@g.us', 'comm-1', 'Grupo 1', 1); }); afterEach(() => { memdb.close(); process.env = envBackup; }); it('publica prompt cuando coverage < 100, grace cumplido y sin cooldown', () => { // snapshot con un resoluble (dígitos) y uno no resoluble (alias sin mapeo) const res = GroupSyncService.reconcileGroupMembers('g1@g.us', [ { userId: '111', isAdmin: false }, { userId: 'alias_lid', isAdmin: false } ], '2025-01-01 00:00:00.000'); expect(res).toEqual(expect.objectContaining({ added: 2 })); const row = memdb.query(`SELECT recipient, message, status FROM response_queue ORDER BY id DESC LIMIT 1`).get() as any; expect(row).toBeTruthy(); expect(row.recipient).toBe('g1@g.us'); expect(String(row.message)).toContain('https://wa.me/555111222'); const g = memdb.query(`SELECT onboarding_prompted_at FROM groups WHERE id = 'g1@g.us'`).get() as any; expect(g).toBeTruthy(); expect(g.onboarding_prompted_at).toBeTruthy(); }); it('omite prompt cuando coverage = 100', () => { // Previo: no debe existir prompt para este grupo const before = memdb.query(`SELECT COUNT(*) AS c FROM response_queue`).get() as any; expect(Number(before.c)).toBe(0); // snapshot totalmente resoluble (dos dígitos) GroupSyncService.reconcileGroupMembers('g1@g.us', [ { userId: '111', isAdmin: false }, { userId: '222', isAdmin: false } ], '2025-01-01 00:00:00.000'); const after = memdb.query(`SELECT COUNT(*) AS c FROM response_queue`).get() as any; expect(Number(after.c)).toBe(0); }); it('omite prompt en modo enforce si el grupo no está allowed', () => { process.env.GROUP_GATING_MODE = 'enforce'; AllowedGroups.setStatus('g1@g.us', 'blocked'); GroupSyncService.reconcileGroupMembers('g1@g.us', [ { userId: '111', isAdmin: false }, { userId: 'alias_lid', isAdmin: false } ], '2025-01-01 00:00:00.000'); const count = memdb.query(`SELECT COUNT(*) AS c FROM response_queue`).get() as any; expect(Number(count.c)).toBe(0); }); });