/** * Unit tests for utils/mailbox.js * Run: node tests/mailbox-path.test.js */ const assert = require('assert'); const fs = require('fs'); const os = require('os'); const path = require('path'); const { normalizeMailbox, buildPath, withMailboxHeaders, parseSharedMailboxEnv, loadMailboxCache, saveMailboxCache, encodeGraphPath, decodeJwtPayload, analyzeTokenScopes } = require('../utils/mailbox'); // --- normalizeMailbox --- assert.deepStrictEqual(normalizeMailbox(undefined).kind, 'me'); assert.deepStrictEqual(normalizeMailbox(null).graphRoot, 'me'); assert.deepStrictEqual(normalizeMailbox('').graphRoot, 'me'); assert.deepStrictEqual(normalizeMailbox('me').graphRoot, 'me'); assert.deepStrictEqual(normalizeMailbox('PRIMARY').graphRoot, 'me'); const shared = normalizeMailbox('HelpDesk@Contoso.com'); assert.strictEqual(shared.kind, 'user'); assert.strictEqual(shared.smtpOrUpn, 'HelpDesk@Contoso.com'); assert.strictEqual(shared.key, 'helpdesk@contoso.com'); assert.strictEqual(shared.graphRoot, 'users/HelpDesk@Contoso.com'); const mailto = normalizeMailbox('mailto:it@springfieldy.org'); assert.strictEqual(mailto.smtpOrUpn, 'it@springfieldy.org'); assert.strictEqual(mailto.graphRoot, 'users/it@springfieldy.org'); const angled = normalizeMailbox(''); assert.strictEqual(angled.smtpOrUpn, 'desk@example.com'); console.log('✅ normalizeMailbox'); // --- buildPath --- assert.strictEqual(buildPath(null, 'messages'), 'me/messages'); assert.strictEqual(buildPath('me', 'mailFolders/inbox/messages'), 'me/mailFolders/inbox/messages'); assert.strictEqual( buildPath('helpdesk@contoso.com', 'messages'), 'users/helpdesk@contoso.com/messages' ); assert.strictEqual( buildPath('helpdesk@contoso.com', '/mailFolders/sentItems/messages'), 'users/helpdesk@contoso.com/mailFolders/sentItems/messages' ); assert.strictEqual(buildPath(shared, 'sendMail'), 'users/HelpDesk@Contoso.com/sendMail'); console.log('✅ buildPath'); // --- encode once (same as callGraphAPI) --- const encoded = encodeGraphPath('users/helpdesk@contoso.com/messages'); assert.strictEqual(encoded, 'users/helpdesk%40contoso.com/messages'); assert.ok(!encoded.includes('%2540'), 'must not double-encode @'); console.log('✅ encodeGraphPath single-encode'); // --- headers --- assert.deepStrictEqual(withMailboxHeaders(null), {}); assert.deepStrictEqual(withMailboxHeaders('me'), {}); assert.deepStrictEqual(withMailboxHeaders('it@x.org'), { 'X-AnchorMailbox': 'it@x.org' }); console.log('✅ withMailboxHeaders'); // --- env parse --- assert.deepStrictEqual(parseSharedMailboxEnv(''), []); assert.deepStrictEqual(parseSharedMailboxEnv(undefined), []); assert.deepStrictEqual( parseSharedMailboxEnv('a@x.com, b@y.com;c@z.com d@w.com'), ['a@x.com', 'b@y.com', 'c@z.com', 'd@w.com'] ); console.log('✅ parseSharedMailboxEnv'); // --- cache round-trip --- const tmp = path.join(os.tmpdir(), `outlook-mcp-mb-cache-${Date.now()}.json`); try { const empty = loadMailboxCache(tmp); assert.strictEqual(empty.mailboxes.length, 0); saveMailboxCache([ { address: 'helpdesk@contoso.com', displayName: 'Help Desk', capabilities: { read: true, listFolders: true, sendAs: 'unverified' }, source: ['env', 'probe'] } ], tmp); const loaded = loadMailboxCache(tmp); assert.strictEqual(loaded.mailboxes.length, 1); assert.strictEqual(loaded.mailboxes[0].address, 'helpdesk@contoso.com'); assert.ok(loaded.mailboxes[0].source.includes('env')); assert.ok(loaded.mailboxes[0].lastProbedAt); // upsert merges sources saveMailboxCache([ { address: 'helpdesk@contoso.com', capabilities: { read: true, listFolders: true, sendAs: 'unverified' }, source: ['candidates'] } ], tmp); const merged = loadMailboxCache(tmp); assert.ok(merged.mailboxes[0].source.includes('candidates')); assert.ok(merged.mailboxes[0].source.includes('env')); console.log('✅ mailbox cache round-trip'); } finally { try { fs.unlinkSync(tmp); } catch { /* ignore */ } } // --- JWT decode / scope analysis --- function makeJwt(payload) { const h = Buffer.from(JSON.stringify({ alg: 'none' })).toString('base64url'); const p = Buffer.from(JSON.stringify(payload)).toString('base64url'); return `${h}.${p}.sig`; } const tok = makeJwt({ scp: 'Mail.Read Mail.Read.Shared User.Read offline_access' }); const analyzed = analyzeTokenScopes(tok); assert.ok(analyzed.present.includes('Mail.Read.Shared')); assert.ok(analyzed.missing.includes('Mail.Send.Shared')); assert.ok(analyzed.missing.includes('Mail.ReadWrite.Shared')); assert.strictEqual(decodeJwtPayload('not-a-jwt'), null); console.log('✅ analyzeTokenScopes / decodeJwtPayload'); console.log('\nAll mailbox-path tests passed.');