/**
 * RED-GREEN tests for F-NEW-PRODUCT-APPROVAL-CHANNEL-EMAIL-SMS (W1.10).
 *
 * Customer-path probe:
 *   - Approval ticket needs a reviewer's attention
 *   - Reviewer A wants email + dashboard; Reviewer B wants SMS; Reviewer C wants
 *     all three
 *   - Dispatcher fans out to each requested channel and reports delivery
 *     outcomes per recipient+channel
 *   - Recipients missing email / phone fail gracefully (no throw)
 *
 * @connexum/ai-governance  F-NEW-PRODUCT-APPROVAL-CHANNEL-EMAIL-SMS
 */
import { describe, it } from 'node:test';
import * as assert from 'node:assert/strict';
import { ApprovalChannelDispatcher, } from './approval-notifications.js';
function ctx() {
    return {
        ticketId: 'tic-001',
        orgId: 'org-x',
        agentId: 'agent-1',
        toolName: 'Bash',
        riskLevel: 'high',
        summary: 'Bash: rm -rf /tmp/scratch',
        approveUrl: 'https://my-cc.io/approve/tic-001?sig=abc',
        denyUrl: 'https://my-cc.io/deny/tic-001?sig=abc',
        slaDeadline: '2026-05-12T15:00:00.000Z',
    };
}
function recordingEmail() {
    const sent = [];
    return {
        sender: {
            async send(p) {
                sent.push(p);
                return { ok: true, messageId: `email-${sent.length}` };
            },
        },
        sent,
    };
}
function recordingSms() {
    const sent = [];
    return {
        sender: {
            async send(p) {
                sent.push(p);
                return { ok: true, messageId: `sms-${sent.length}` };
            },
        },
        sent,
    };
}
describe('F-NEW-PRODUCT-APPROVAL-CHANNEL-EMAIL-SMS (W1.10)', () => {
    it('email channel sends to the email transport and reports delivery', async () => {
        const e = recordingEmail();
        const dispatcher = new ApprovalChannelDispatcher({ emailSender: e.sender });
        const outcomes = await dispatcher.dispatch(ctx(), [
            {
                reviewerId: 'alice',
                name: 'Alice',
                email: 'alice@example.com',
                channels: ['email'],
            },
        ]);
        assert.equal(outcomes.length, 1);
        assert.equal(outcomes[0].channel, 'email');
        assert.equal(outcomes[0].delivered, true);
        assert.equal(outcomes[0].providerMessageId, 'email-1');
        assert.equal(e.sent.length, 1);
        assert.equal(e.sent[0].to, 'alice@example.com');
        assert.match(e.sent[0].subject, /Approval needed/);
        assert.match(e.sent[0].html, /Alice/);
        assert.match(e.sent[0].html, /Bash/);
    });
    it('SMS channel sends to the SMS transport and reports delivery', async () => {
        const s = recordingSms();
        const dispatcher = new ApprovalChannelDispatcher({ smsSender: s.sender });
        const outcomes = await dispatcher.dispatch(ctx(), [
            {
                reviewerId: 'bob',
                name: 'Bob',
                phoneE164: '+15551234567',
                channels: ['sms'],
            },
        ]);
        assert.equal(outcomes.length, 1);
        assert.equal(outcomes[0].channel, 'sms');
        assert.equal(outcomes[0].delivered, true);
        assert.equal(s.sent.length, 1);
        assert.equal(s.sent[0].to, '+15551234567');
        assert.match(s.sent[0].body, /my-cc/);
        assert.match(s.sent[0].body, /Bash/);
        assert.match(s.sent[0].body, /Approve:/);
    });
    it('dashboard channel calls the dashboard sink and reports delivery', async () => {
        const dashboardCalls = [];
        const dispatcher = new ApprovalChannelDispatcher({
            dashboardSink: (_c, r) => dashboardCalls.push(r),
        });
        const outcomes = await dispatcher.dispatch(ctx(), [
            { reviewerId: 'carol', name: 'Carol', channels: ['dashboard'] },
        ]);
        assert.equal(outcomes[0].delivered, true);
        assert.equal(outcomes[0].channel, 'dashboard');
        assert.equal(dashboardCalls.length, 1);
        assert.equal(dashboardCalls[0].reviewerId, 'carol');
    });
    it('fan-out: one reviewer with all 3 channels emits 3 outcomes', async () => {
        const e = recordingEmail();
        const s = recordingSms();
        const dashboardCalls = [];
        const dispatcher = new ApprovalChannelDispatcher({
            emailSender: e.sender,
            smsSender: s.sender,
            dashboardSink: (_c, r) => dashboardCalls.push(r),
        });
        const outcomes = await dispatcher.dispatch(ctx(), [
            {
                reviewerId: 'dave',
                name: 'Dave',
                email: 'dave@example.com',
                phoneE164: '+15559999999',
                channels: ['dashboard', 'email', 'sms'],
            },
        ]);
        assert.equal(outcomes.length, 3);
        assert.ok(outcomes.every((o) => o.delivered));
        assert.equal(e.sent.length, 1);
        assert.equal(s.sent.length, 1);
        assert.equal(dashboardCalls.length, 1);
    });
    it('fail-gracefully: email channel with missing email reports delivered=false', async () => {
        const dispatcher = new ApprovalChannelDispatcher({ emailSender: recordingEmail().sender });
        const outcomes = await dispatcher.dispatch(ctx(), [
            { reviewerId: 'eve', name: 'Eve', channels: ['email'] /* no email */ },
        ]);
        assert.equal(outcomes[0].delivered, false);
        assert.match(outcomes[0].error ?? '', /no email/);
    });
    it('fail-gracefully: SMS channel with missing phone reports delivered=false', async () => {
        const dispatcher = new ApprovalChannelDispatcher({ smsSender: recordingSms().sender });
        const outcomes = await dispatcher.dispatch(ctx(), [
            { reviewerId: 'frank', name: 'Frank', channels: ['sms'] /* no phone */ },
        ]);
        assert.equal(outcomes[0].delivered, false);
        assert.match(outcomes[0].error ?? '', /no phone number/);
    });
    it('default channel is dashboard when channels not specified', async () => {
        const dashboardCalls = [];
        const dispatcher = new ApprovalChannelDispatcher({
            dashboardSink: (_c, r) => dashboardCalls.push(r),
        });
        const outcomes = await dispatcher.dispatch(ctx(), [
            { reviewerId: 'gina', name: 'Gina' /* no channels */ },
        ]);
        assert.equal(outcomes.length, 1);
        assert.equal(outcomes[0].channel, 'dashboard');
        assert.equal(dashboardCalls.length, 1);
    });
    it('NOOP senders return delivered=false with structured error (no throw)', async () => {
        const dispatcher = new ApprovalChannelDispatcher();
        const outcomes = await dispatcher.dispatch(ctx(), [
            { reviewerId: 'h', name: 'H', email: 'h@example.com', channels: ['email'] },
            { reviewerId: 'i', name: 'I', phoneE164: '+15550000000', channels: ['sms'] },
        ]);
        assert.equal(outcomes.length, 2);
        assert.equal(outcomes[0].delivered, false);
        assert.match(outcomes[0].error ?? '', /no email sender/);
        assert.equal(outcomes[1].delivered, false);
        assert.match(outcomes[1].error ?? '', /no SMS sender/);
    });
    it('email body HTML escapes hostile names + tool inputs', async () => {
        const e = recordingEmail();
        const dispatcher = new ApprovalChannelDispatcher({ emailSender: e.sender });
        const hostileCtx = ctx();
        hostileCtx.agentId = '<script>alert(1)</script>';
        hostileCtx.summary = 'rm -rf "<sneaky>"';
        await dispatcher.dispatch(hostileCtx, [
            {
                reviewerId: 'j',
                name: '<img onerror=1>',
                email: 'j@example.com',
                channels: ['email'],
            },
        ]);
        assert.ok(!e.sent[0].html.includes('<script>'));
        assert.ok(!e.sent[0].html.includes('<img onerror'));
        assert.ok(e.sent[0].html.includes('&lt;script&gt;'));
    });
});
//# sourceMappingURL=approval-notifications.test.js.map