import assert from 'node:assert/strict'; import { mkdtempSync } from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { test } from 'node:test'; import { createDomain, createImportedInboundMessage, getInboundMailbox, getInboundMailboxProtocolMessage, getInboundMessage, hasImportedInboundMessage, initDatabase, listInboundMailboxProtocolMessages, searchInboundMessages, seedAdminUser, upsertImportedInboundMailbox, verifyInboundMailboxCredential } from '../src/db.js'; import { createDkimKeyPair } from '../src/dkim.js'; import { isLegacyPasswordHash } from '../src/password-hash.js'; function tempDataDir() { return mkdtempSync(path.join(os.tmpdir(), 'mailhub-vesta-import-')); } function createTestDomain(userId, domainName) { const keys = createDkimKeyPair(); return createDomain(userId, { domain: domainName, selector: 'mhimport', verificationToken: 'import-verification-token', dkimPublic: keys.publicKey, dkimPrivate: keys.privateKey, senderHost: `mail.${domainName}`, sendingIp: '192.0.2.10', spfExtra: '', dmarcPolicy: 'none', dmarcRua: '' }); } test('imports Vesta mailbox hashes without plaintext and upgrades after successful login', () => { initDatabase(tempDataDir(), 'import-test-secret'); const user = seedAdminUser({ username: 'admin', email: 'admin@example.test', password: 'admin-password' }); createTestDomain(user.id, 'legacy.example'); const legacyHash = '{MD5}$1$hfT7jp2q$G3yf0NUx7mUkX.LIFWQxN.'; const mailbox = upsertImportedInboundMailbox(user.id, { address: 'inbox@legacy.example', passwordHash: legacyHash, quotaMb: null, status: 'active' }); const before = getInboundMailbox(mailbox.id, user.id, { includeHash: true, includeSecret: true }); assert.equal(before.passwordHash, legacyHash); assert.equal(before.passwordSecret, ''); assert.equal(before.passwordRecoverable, false); assert.equal(verifyInboundMailboxCredential(mailbox.address, 'wrong password'), null); assert.ok(verifyInboundMailboxCredential(mailbox.address, 'password')); const upgraded = getInboundMailbox(mailbox.id, user.id, { includeHash: true, includeSecret: true }); assert.match(upgraded.passwordHash, /^scrypt\$/); assert.equal(isLegacyPasswordHash(upgraded.passwordHash), false); assert.equal(upgraded.passwordSecret, ''); upsertImportedInboundMailbox(user.id, { address: mailbox.address, passwordHash: legacyHash }); const afterResume = getInboundMailbox(mailbox.id, user.id, { includeHash: true }); assert.equal(afterResume.passwordHash, upgraded.passwordHash); }); test('imports raw Maildir bytes, flags, timestamps and skips the same source key', () => { initDatabase(tempDataDir(), 'import-test-secret'); const user = seedAdminUser({ username: 'admin', email: 'admin@example.test', password: 'admin-password' }); createTestDomain(user.id, 'mail.example'); const mailbox = upsertImportedInboundMailbox(user.id, { address: 'archive@mail.example', passwordHash: '{MD5}$1$hfT7jp2q$G3yf0NUx7mUkX.LIFWQxN.' }); const rawMessageBytes = Buffer.from('From: sender@example.test\r\nSubject: imported\r\n\r\nbody', 'utf8'); const payload = { importSource: 'vesta:ali.ss5.xyz', sourceKey: 'maildir-v1-source-key', folder: 'Projects/2026', sender: 'sender@example.test', recipients: [mailbox.address], subject: 'imported', messageId: '', rawMessageBytes, textBody: 'body', flags: ['\\Seen', '\\Flagged'], keywords: ['$Forwarded'], receivedAt: '2026-07-01T12:34:56.000Z' }; const first = createImportedInboundMessage(mailbox, payload); const second = createImportedInboundMessage(mailbox, payload); assert.equal(first.created, true); assert.equal(second.created, false); assert.equal(second.message.id, first.message.id); assert.equal(hasImportedInboundMessage(payload.importSource, payload.sourceKey), true); const message = getInboundMessage(user.id, first.message.id); assert.equal(message.rawMessage, rawMessageBytes.toString('utf8')); assert.equal(message.read, true); assert.deepEqual(message.flags, ['\\Seen', '\\Flagged']); assert.deepEqual(message.keywords, ['$Forwarded']); assert.equal(message.folder, 'Projects/2026'); assert.equal(message.receivedAt, payload.receivedAt); const [summary] = listInboundMailboxProtocolMessages(mailbox, { folder: payload.folder }); assert.equal(summary.rawMessageSize, rawMessageBytes.length); assert.equal(summary.pop3MessageSize, rawMessageBytes.length + 2); assert.equal(Object.hasOwn(summary, 'rawMessage'), false); assert.equal(Object.hasOwn(summary, 'rawMessageBytes'), false); const hydrated = getInboundMailboxProtocolMessage(mailbox, summary.id, { folder: payload.folder }); assert.ok(hydrated.rawMessageBytes.equals(rawMessageBytes)); const searchResult = searchInboundMessages(user.id, { folder: payload.folder }); assert.equal(searchResult.messages[0].rawMessageSize, rawMessageBytes.length); assert.equal(Object.hasOwn(searchResult.messages[0], 'rawMessageBytes'), false); });