87 lines
3.3 KiB
JavaScript
87 lines
3.3 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
import test from 'node:test'
|
|
import {
|
|
encryptEmailPassword,
|
|
fetchEmailMessages,
|
|
markEmailUidsSeen,
|
|
sendEmailMessage,
|
|
verifyEmailConnection,
|
|
} from '../src/email.js'
|
|
|
|
const enabled = process.env.RUN_EMAIL_PROTOCOL_TEST === '1'
|
|
|
|
function channel(account) {
|
|
return {
|
|
id: `protocol-${account}`,
|
|
type: 'email',
|
|
account,
|
|
displayName: account.startsWith('sender') ? 'Lingxi Sender' : 'Lingxi Receiver',
|
|
encryptedPassword: encryptEmailPassword('pwd'),
|
|
imapHost: process.env.EMAIL_TEST_HOST || '127.0.0.1',
|
|
imapPort: Number(process.env.EMAIL_TEST_IMAP_PORT || 3143),
|
|
imapSecure: false,
|
|
imapFolder: 'INBOX',
|
|
smtpHost: process.env.EMAIL_TEST_HOST || '127.0.0.1',
|
|
smtpPort: Number(process.env.EMAIL_TEST_SMTP_PORT || 3025),
|
|
smtpSecure: false,
|
|
syncDays: 7,
|
|
}
|
|
}
|
|
|
|
async function waitForMessage(target, predicate) {
|
|
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
const result = await fetchEmailMessages(target, { full: true })
|
|
const message = result.messages.find(predicate)
|
|
if (message) return { message, result }
|
|
await new Promise((resolve) => setTimeout(resolve, 150))
|
|
}
|
|
throw new Error('等待邮件进入 IMAP INBOX 超时')
|
|
}
|
|
|
|
test('真实 SMTP 发件、IMAP 收件、附件解析、已读与回复头', { skip: !enabled }, async () => {
|
|
process.env.JWT_SECRET ||= 'lingxi-email-protocol-test-secret'
|
|
const sender = channel('sender@local.test')
|
|
const receiver = channel('receiver@local.test')
|
|
const subject = `Lingxi protocol ${Date.now()}`
|
|
|
|
const senderCheck = await verifyEmailConnection(sender, 'pwd')
|
|
const receiverCheck = await verifyEmailConnection(receiver, 'pwd')
|
|
assert.ok(senderCheck.latencyMs >= 0)
|
|
assert.ok(receiverCheck.latencyMs >= 0)
|
|
assert.ok(!sender.encryptedPassword.includes('pwd'))
|
|
|
|
const sent = await sendEmailMessage(sender, {
|
|
to: receiver.account,
|
|
subject,
|
|
text: 'Lingxi IMAP/SMTP real protocol message',
|
|
attachments: [{
|
|
filename: 'lingxi-proof.txt',
|
|
content: Buffer.from('real email attachment'),
|
|
contentType: 'text/plain',
|
|
}],
|
|
})
|
|
assert.ok(sent.messageId)
|
|
|
|
const received = await waitForMessage(receiver, (message) => message.subject === subject)
|
|
assert.equal(received.message.text, 'Lingxi IMAP/SMTP real protocol message')
|
|
assert.equal(received.message.from.email, sender.account)
|
|
assert.equal(received.message.attachments.length, 1)
|
|
assert.equal(received.message.attachments[0].name, 'lingxi-proof.txt')
|
|
assert.equal(Buffer.from(received.message.attachments[0].base64, 'base64').toString(), 'real email attachment')
|
|
|
|
await markEmailUidsSeen(receiver, [received.message.uid])
|
|
const marked = await fetchEmailMessages(receiver, { full: true })
|
|
assert.equal(marked.messages.find((message) => message.uid === received.message.uid)?.seen, true)
|
|
|
|
const reply = await sendEmailMessage(receiver, {
|
|
to: sender.account,
|
|
subject: `Re: ${subject}`,
|
|
text: 'Reply received by Lingxi',
|
|
inReplyTo: received.message.messageId,
|
|
references: [received.message.messageId],
|
|
})
|
|
const replyReceived = await waitForMessage(sender, (message) => message.messageId === reply.messageId)
|
|
assert.equal(replyReceived.message.inReplyTo, received.message.messageId)
|
|
assert.ok(replyReceived.message.references.includes(received.message.messageId))
|
|
})
|