godcrm/tests/helpers/emailEmulation.js
GOD CRM Release f89e074dd1
Some checks failed
CI / Lint / Typecheck / Test / Build (push) Has been cancelled
CI / PostgreSQL Integration Tests (push) Has been cancelled
GOD CRM — public scrubbed snapshot
Governed substrate for autonomous agents: scoped identity (passports),
audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
2026-08-10 04:01:45 +03:00

167 lines
4.4 KiB
JavaScript

/**
* 📧 Email Emulation Helper
*
* Для тестирования email без реальной отправки.
* Использует локальный Postfix (/var/mail/).
*
* Сервер использует 127.0.0.1:25 (local postfix) для отправки.
* Письма попадают в /var/mail/root если получатель локальный.
*
* Для тестовых emails (@test.godcrm.local) — они не доставляются реально,
* поэтому можно либо:
* 1. Мокать SMTPService в тестах
* 2. Читать /var/mail/root для писем на локальные адреса
* 3. Использовать MailHog/MailCatcher для перехвата
*/
import fs from 'fs';
import path from 'path';
const MAIL_FILE = '/var/mail/root';
const MAIL_LOG = '/var/log/mail.log';
/**
* Читает последние N писем из локального mail spool
*/
export function readLocalMail(limit = 10) {
try {
if (!fs.existsSync(MAIL_FILE)) {
return { success: false, error: 'Mail file not found' };
}
const content = fs.readFileSync(MAIL_FILE, 'utf8');
const emails = content.split(/^From /m).filter(Boolean);
const parsed = emails.slice(-limit).map(raw => {
const lines = raw.split('\n');
const headers = {};
let body = '';
let inBody = false;
for (const line of lines) {
if (inBody) {
body += line + '\n';
} else if (line === '') {
inBody = true;
} else {
const match = line.match(/^([^:]+):\s*(.*)$/);
if (match) {
headers[match[1].toLowerCase()] = match[2];
}
}
}
return {
from: headers['from'] || '',
to: headers['to'] || '',
subject: headers['subject'] || '',
date: headers['date'] || '',
body: body.slice(0, 500) // First 500 chars
};
});
return { success: true, emails: parsed };
} catch (error) {
return { success: false, error: error.message };
}
}
/**
* Ищет письмо по email или теме
*/
export function findEmail(searchTerm) {
const result = readLocalMail(50);
if (!result.success) return result;
const found = result.emails.filter(e =>
e.to.includes(searchTerm) ||
e.subject.includes(searchTerm) ||
e.from.includes(searchTerm)
);
return { success: true, found };
}
/**
* Извлекает verification code из письма
*/
export function extractVerificationCode(emailBody) {
// Типичные паттерны для verification codes
const patterns = [
/код[:\s]+(\d{6})/i,
/code[:\s]+(\d{6})/i,
/verification[:\s]+(\d{6})/i,
/\b(\d{6})\b/ // Fallback: любые 6 цифр
];
for (const pattern of patterns) {
const match = emailBody.match(pattern);
if (match) return match[1];
}
return null;
}
/**
* Мок для SMTPService.sendTestEmail
* Используется в unit тестах
*/
export function createEmailMock() {
const sentEmails = [];
return {
sendTestEmail: async (config, toEmail, code) => {
sentEmails.push({
timestamp: new Date().toISOString(),
to: toEmail,
code,
config
});
return { success: true, messageId: `mock-${Date.now()}` };
},
getSentEmails: () => [...sentEmails],
getLastEmail: () => sentEmails[sentEmails.length - 1],
findByRecipient: (email) => sentEmails.filter(e => e.to === email),
clear: () => sentEmails.length = 0
};
}
/**
* Проверяет, настроен ли локальный mail transport
*/
export async function checkEmailSetup() {
const checks = {
mailFile: fs.existsSync(MAIL_FILE),
mailLog: fs.existsSync(MAIL_LOG),
smtpConfig: false
};
try {
const configPath = path.join(process.cwd(), 'backend', 'smtp-config.json');
if (fs.existsSync(configPath)) {
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
checks.smtpConfig = config.accounts?.length > 0;
checks.smtpAccounts = config.accounts?.map(a => ({
name: a.name,
host: a.host,
isLocal: a.isLocal || a.host === '127.0.0.1'
}));
}
} catch (e) {
checks.smtpConfig = false;
}
return checks;
}
export default {
readLocalMail,
findEmail,
extractVerificationCode,
createEmailMock,
checkEmailSetup
};