/** 邮件契约 —— 账号入参按 kind 分两形,默认值与视图不带密文 */ import { describe, expect, it } from 'vitest'; import { mailAccountInputSchema, mailAccountViewSchema, mailSendInputSchema, MAIL_ACCOUNT_KINDS, } from '../mail.ts'; describe('mailAccountInputSchema', () => { it('imap-smtp:只给地址与名字,腾讯的服务器 / 端口 / TLS / 文件夹全部有默认值', () => { const parsed = mailAccountInputSchema.parse({ kind: 'imap-smtp', name: '销售信箱', address: 'sales@we.com' }); expect(parsed.kind).toBe('imap-smtp'); if (parsed.kind !== 'imap-smtp') throw new Error('unreachable'); expect(parsed.settings).toEqual({ imapHost: 'imap.exmail.qq.com', imapPort: 993, imapTls: true, smtpHost: 'smtp.exmail.qq.com', smtpPort: 465, smtpTls: true, folder: 'INBOX', }); expect(parsed.enabled).toBe(true); expect(parsed.isDefault).toBe(false); expect(parsed.pollIntervalSec).toBe(30); expect(parsed.channelId).toBeNull(); expect(parsed.backfillFrom).toBeNull(); expect(parsed.secret).toBeUndefined(); }); it('wecom:corpId 必填,地址不由人填(探测回填)', () => { const parsed = mailAccountInputSchema.parse({ kind: 'wecom', name: '应用邮箱', settings: { corpId: 'ww123' } }); if (parsed.kind !== 'wecom') throw new Error('unreachable'); expect(parsed.settings.apiBase).toBe('https://qyapi.weixin.qq.com'); expect('address' in parsed).toBe(false); expect(mailAccountInputSchema.safeParse({ kind: 'wecom', name: 'x', settings: {} }).success).toBe(false); }); it('轮询间隔下限 10 秒;地址必须是带 TLD 的邮箱;kind 只认两种', () => { expect(mailAccountInputSchema.safeParse({ kind: 'imap-smtp', name: 'a', address: 'a@b.com', pollIntervalSec: 5 }).success).toBe(false); expect(mailAccountInputSchema.safeParse({ kind: 'imap-smtp', name: 'a', address: 'not-mail' }).success).toBe(false); // 漏了 TLD 的地址(sales@corp)是打字错误,在 API 边界就拦下 —— 别拖到 SMTP 才炸 expect(mailAccountInputSchema.safeParse({ kind: 'imap-smtp', name: 'a', address: 'sales@corp' }).success).toBe(false); expect(mailAccountInputSchema.safeParse({ kind: 'pop3', name: 'a' }).success).toBe(false); expect(MAIL_ACCOUNT_KINDS).toEqual(['imap-smtp', 'wecom']); }); }); describe('mailAccountViewSchema', () => { it('视图只有 secretBound,没有任何密文 / 密码字段', () => { const keys = Object.keys(mailAccountViewSchema.options[0]!.shape); expect(keys).toContain('secretBound'); expect(keys.some((k) => /cipher|password|secret$/i.test(k))).toBe(false); }); }); describe('mailSendInputSchema', () => { it('收件人至少一个、主题必填;附件默认空数组;requestedBy 必带 kind', () => { const ok = mailSendInputSchema.safeParse({ to: ['a@b.com'], subject: 'hi', text: 'x', requestedBy: { kind: 'ops' } }); expect(ok.success).toBe(true); if (ok.success) expect(ok.data.attachments).toEqual([]); expect(mailSendInputSchema.safeParse({ to: [], subject: 'hi', text: 'x', requestedBy: { kind: 'ops' } }).success).toBe(false); expect(mailSendInputSchema.safeParse({ to: ['a@b.com'], subject: 'hi', text: 'x' }).success).toBe(false); }); });