const test = require('node:test'); const assert = require('node:assert/strict'); const { buildHotmailGraphMessagesUrl, extractVerificationCodeFromMessage, filterHotmailAccountsByUsage, extractVerificationCode, getLatestHotmailMessage, getHotmailBulkActionLabel, getHotmailListToggleLabel, getHotmailGraphRequestConfig, getHotmailVerificationPollConfig, getHotmailVerificationRequestTimestamp, normalizeHotmailMailboxId, normalizeHotmailMailApiMessages, parseHotmailImportText, pickHotmailAccountForRun, pickVerificationMessage, pickVerificationMessageWithFallback, pickVerificationMessageWithTimeFallback, shouldClearHotmailCurrentSelection, upsertHotmailAccountInList, } = require('../hotmail-utils.js'); test('pickHotmailAccountForRun prefers authorized account with oldest lastUsedAt', () => { const now = Date.UTC(2026, 3, 10, 10, 0, 0); const accounts = [ { id: 'recent', email: 'recent@hotmail.com', status: 'authorized', refreshToken: 'rt-recent', lastUsedAt: now - 1_000, }, { id: 'oldest', email: 'oldest@hotmail.com', status: 'authorized', refreshToken: 'rt-oldest', lastUsedAt: now - 50_000, }, { id: 'pending', email: 'pending@hotmail.com', status: 'pending', refreshToken: '', lastUsedAt: 0, }, ]; const selected = pickHotmailAccountForRun(accounts, { now }); assert.equal(selected.id, 'oldest'); }); test('pickHotmailAccountForRun skips used accounts but ignores legacy enabled flag', () => { const accounts = [ { id: 'disabled', email: 'disabled@hotmail.com', status: 'authorized', refreshToken: 'rt-disabled', enabled: false, used: false, lastUsedAt: 1, }, { id: 'used', email: 'used@hotmail.com', status: 'authorized', refreshToken: 'rt-used', enabled: true, used: true, lastUsedAt: 0, }, { id: 'available', email: 'available@hotmail.com', status: 'authorized', refreshToken: 'rt-available', enabled: true, used: false, lastUsedAt: 2, }, ]; const selected = pickHotmailAccountForRun(accounts, {}); assert.equal(selected.id, 'disabled'); }); test('pickHotmailAccountForRun returns null for used-only pools', () => { assert.equal( pickHotmailAccountForRun([{ id: 'used-only', email: 'used-only@hotmail.com', status: 'authorized', refreshToken: 'rt-used-only', used: true, lastUsedAt: 0, }], {}), null ); }); test('pickHotmailAccountForRun falls back to never-used authorized account first', () => { const accounts = [ { id: 'used', email: 'used@hotmail.com', status: 'authorized', refreshToken: 'rt-used', lastUsedAt: Date.UTC(2026, 3, 10, 9, 0, 0), }, { id: 'fresh', email: 'fresh@hotmail.com', status: 'authorized', refreshToken: 'rt-fresh', lastUsedAt: 0, }, ]; const selected = pickHotmailAccountForRun(accounts, { now: Date.UTC(2026, 3, 10, 10, 0, 0) }); assert.equal(selected.id, 'fresh'); }); test('upsertHotmailAccountInList replaces matching account state by id', () => { const accounts = [ { id: 'active', email: 'active@hotmail.com', status: 'authorized', used: false, }, { id: 'other', email: 'other@hotmail.com', status: 'authorized', used: false, }, ]; const nextAccounts = upsertHotmailAccountInList(accounts, { id: 'active', email: 'active@hotmail.com', status: 'authorized', used: true, }); assert.deepEqual(nextAccounts, [ { id: 'active', email: 'active@hotmail.com', status: 'authorized', used: true, }, { id: 'other', email: 'other@hotmail.com', status: 'authorized', used: false, }, ]); }); test('shouldClearHotmailCurrentSelection returns true only when account becomes used', () => { assert.equal(shouldClearHotmailCurrentSelection({ id: 'used', used: true, }), true); assert.equal(shouldClearHotmailCurrentSelection({ id: 'available', used: false, }), false); }); test('extractVerificationCode returns first six-digit code from multilingual mail text', () => { assert.equal(extractVerificationCode('你的 ChatGPT 验证码为 370794,请勿泄露。'), '370794'); assert.equal(extractVerificationCode('Your verification code is 654321.'), '654321'); assert.equal(extractVerificationCode('No code here'), null); }); test('extractVerificationCodeFromMessage reads code from the latest message subject or preview', () => { assert.equal( extractVerificationCodeFromMessage({ subject: '你的 ChatGPT 代码为 192742', bodyPreview: 'OpenAI 验证邮件', from: { emailAddress: { address: 'noreply@openai.com' } }, }), '192742' ); assert.equal( extractVerificationCodeFromMessage({ subject: 'OpenAI security message', bodyPreview: 'Your verification code is 654321.', from: { emailAddress: { address: 'noreply@openai.com' } }, }), '654321' ); }); test('getHotmailListToggleLabel reflects expanded state and account count', () => { assert.equal(getHotmailListToggleLabel(false, 0), '展开列表'); assert.equal(getHotmailListToggleLabel(false, 7), '展开列表(7)'); assert.equal(getHotmailListToggleLabel(true, 7), '收起列表(7)'); }); test('filterHotmailAccountsByUsage can pick only used accounts or return all accounts', () => { const accounts = [ { id: 'used-1', email: 'used-1@hotmail.com', used: true }, { id: 'fresh-1', email: 'fresh-1@hotmail.com', used: false }, { id: 'used-2', email: 'used-2@hotmail.com', used: true }, ]; assert.deepEqual( filterHotmailAccountsByUsage(accounts, 'used').map((account) => account.id), ['used-1', 'used-2'] ); assert.deepEqual( filterHotmailAccountsByUsage(accounts, 'all').map((account) => account.id), ['used-1', 'fresh-1', 'used-2'] ); }); test('getHotmailBulkActionLabel reflects action type and count', () => { assert.equal(getHotmailBulkActionLabel('used', 0), '清空已用'); assert.equal(getHotmailBulkActionLabel('used', 3), '清空已用(3)'); assert.equal(getHotmailBulkActionLabel('all', 5), '全部删除(5)'); }); test('getLatestHotmailMessage picks the newest received mail', () => { const latest = getLatestHotmailMessage([ { id: 'older', subject: 'older', receivedDateTime: '2026-04-11T00:01:00.000Z', }, { id: 'newest', subject: 'newest', receivedDateTime: '2026-04-11T00:05:00.000Z', }, { id: 'middle', subject: 'middle', receivedDateTime: '2026-04-11T00:03:00.000Z', }, ]); assert.equal(latest.id, 'newest'); }); test('pickVerificationMessage filters by time, sender, subject, and excluded codes', () => { const messages = [ { id: 'old-mail', subject: 'Your code is 111111', from: { emailAddress: { address: 'noreply@openai.com' } }, bodyPreview: '111111', receivedDateTime: '2026-04-10T09:00:00.000Z', }, { id: 'wrong-sender', subject: 'Your code is 222222', from: { emailAddress: { address: 'noreply@example.com' } }, bodyPreview: '222222', receivedDateTime: '2026-04-10T10:01:00.000Z', }, { id: 'good-mail', subject: 'ChatGPT verification code 333333', from: { emailAddress: { address: 'noreply@openai.com' } }, bodyPreview: 'Use 333333 to continue', receivedDateTime: '2026-04-10T10:02:00.000Z', }, { id: 'excluded-mail', subject: 'ChatGPT verification code 444444', from: { emailAddress: { address: 'noreply@openai.com' } }, bodyPreview: 'Use 444444 to continue', receivedDateTime: '2026-04-10T10:03:00.000Z', }, ]; const match = pickVerificationMessage(messages, { afterTimestamp: Date.UTC(2026, 3, 10, 10, 0, 0), senderFilters: ['openai', 'noreply'], subjectFilters: ['verification', 'code', 'chatgpt'], excludeCodes: ['444444'], }); assert.equal(match.message.id, 'good-mail'); assert.equal(match.code, '333333'); }); test('pickVerificationMessageWithFallback no longer matches arbitrary recent mails when filters miss', () => { const messages = [ { id: 'login-mail', subject: 'Use this security code to continue 555666', from: { emailAddress: { address: 'account-security@openai.com' } }, bodyPreview: 'Your one-time security code is 555666', receivedDateTime: '2026-04-10T10:05:00.000Z', }, ]; const result = pickVerificationMessageWithFallback(messages, { afterTimestamp: Date.UTC(2026, 3, 10, 10, 0, 0), senderFilters: ['noreply'], subjectFilters: ['verification'], excludeCodes: [], }); assert.equal(result.match, null); assert.equal(result.usedRelaxedFilters, false); assert.equal(result.usedTimeFallback, false); }); test('pickVerificationMessageWithTimeFallback can ignore afterTimestamp while keeping sender and subject filters', () => { const messages = [ { id: 'slightly-old-mail', subject: '你的 ChatGPT 代码为 141735', from: { emailAddress: { address: 'unknown' } }, bodyPreview: 'OpenAI logo ...', receivedDateTime: '2026-04-10T10:00:02.000Z', }, ]; const result = pickVerificationMessageWithTimeFallback(messages, { afterTimestamp: Date.UTC(2026, 3, 10, 10, 0, 10), senderFilters: ['openai', 'noreply'], subjectFilters: ['verify', 'verification', 'code'], excludeCodes: [], }); assert.equal(result.match.message.id, 'slightly-old-mail'); assert.equal(result.match.code, '141735'); assert.equal(result.usedRelaxedFilters, false); assert.equal(result.usedTimeFallback, true); }); test('buildHotmailGraphMessagesUrl targets the official Microsoft Graph mailbox endpoint', () => { const url = new URL(buildHotmailGraphMessagesUrl({ mailbox: 'Junk', })); assert.equal(url.origin + url.pathname, 'https://graph.microsoft.com/v1.0/me/mailFolders/junkemail/messages'); assert.equal(url.searchParams.get('$top'), '10'); assert.equal(url.searchParams.get('$orderby'), 'receivedDateTime desc'); assert.match(url.searchParams.get('$select'), /subject/); assert.match(url.searchParams.get('$select'), /receivedDateTime/); }); test('normalizeHotmailMailboxId maps supported mailbox labels to Graph folder ids', () => { assert.equal(normalizeHotmailMailboxId('INBOX'), 'inbox'); assert.equal(normalizeHotmailMailboxId('Junk'), 'junkemail'); assert.equal(normalizeHotmailMailboxId('junkemail'), 'junkemail'); }); test('normalizeHotmailMailApiMessages maps third-party payload fields into verification message shape', () => { const messages = normalizeHotmailMailApiMessages([ { id: 'mail-1', from: 'noreply@openai.com', subject: 'ChatGPT verification code', text: 'Use 135790 to continue', date: '2026-04-10T10:02:00.000Z', }, { message_id: 'mail-2', sender_email: 'alerts@example.com', title: 'Ignored', body: 'No code here', received_at: '2026-04-10T10:03:00.000Z', }, ]); assert.deepEqual(messages, [ { id: 'mail-1', subject: 'ChatGPT verification code', from: { emailAddress: { address: 'noreply@openai.com' } }, bodyPreview: 'Use 135790 to continue', receivedDateTime: '2026-04-10T10:02:00.000Z', }, { id: 'mail-2', subject: 'Ignored', from: { emailAddress: { address: 'alerts@example.com' } }, bodyPreview: 'No code here', receivedDateTime: '2026-04-10T10:03:00.000Z', }, ]); }); test('getHotmailVerificationPollConfig gives Hotmail a slower initial wait and longer polling window', () => { assert.deepEqual(getHotmailVerificationPollConfig(4), { initialDelayMs: 5000, maxAttempts: 12, intervalMs: 5000, requestFreshCodeFirst: false, ignorePersistedLastCode: true, }); assert.deepEqual(getHotmailVerificationPollConfig(7), { initialDelayMs: 5000, maxAttempts: 12, intervalMs: 5000, requestFreshCodeFirst: false, ignorePersistedLastCode: true, }); }); test('getHotmailVerificationRequestTimestamp prefers actual request timestamps with a safety buffer', () => { const signupRequestedAt = Date.UTC(2026, 3, 10, 12, 0, 30); const loginRequestedAt = Date.UTC(2026, 3, 10, 12, 5, 45); assert.equal( getHotmailVerificationRequestTimestamp(4, { signupVerificationRequestedAt: signupRequestedAt, flowStartTime: signupRequestedAt - 60_000, }), signupRequestedAt - 15_000 ); assert.equal( getHotmailVerificationRequestTimestamp(7, { loginVerificationRequestedAt: loginRequestedAt, lastEmailTimestamp: loginRequestedAt - 120_000, flowStartTime: loginRequestedAt - 300_000, }), loginRequestedAt - 15_000 ); }); test('getHotmailGraphRequestConfig defines Microsoft Graph request defaults', () => { assert.deepEqual(getHotmailGraphRequestConfig(), { timeoutMs: 15000, pageSize: 10, scopes: [ 'offline_access', 'https://graph.microsoft.com/Mail.Read', 'https://graph.microsoft.com/User.Read', ], tokenUrl: 'https://login.microsoftonline.com/consumers/oauth2/v2.0/token', messageFields: [ 'id', 'internetMessageId', 'subject', 'from', 'bodyPreview', 'receivedDateTime', ], }); }); test('parseHotmailImportText parses account lines in email----password----clientId----token format', () => { const parsed = parseHotmailImportText(` 账号----密码----ID----Token JohnRodriguez5425@hotmail.com----nb4ta1OK----9e5f94bc-e8a4-4e73-b8be-63364c29d753----refresh-token-1 alice@hotmail.com----pass-2----client-2----refresh-token-2 `.trim()); assert.deepEqual(parsed, [ { email: 'JohnRodriguez5425@hotmail.com', password: 'nb4ta1OK', clientId: '9e5f94bc-e8a4-4e73-b8be-63364c29d753', refreshToken: 'refresh-token-1', }, { email: 'alice@hotmail.com', password: 'pass-2', clientId: 'client-2', refreshToken: 'refresh-token-2', }, ]); });