From 411fecc8c685f6e778b334d8d605d44bba492e52 Mon Sep 17 00:00:00 2001 From: xcs Date: Tue, 25 Aug 2026 23:51:57 +0800 Subject: [PATCH] =?UTF-8?q?=E9=82=AE=E4=BB=B6=E5=A5=91=E7=BA=A6=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D:=E5=9C=B0=E5=9D=80=E6=A0=A1=E9=AA=8C=E4=BF=9D?= =?UTF-8?q?=E6=8C=81=E4=B8=A5=E6=A0=BC=20z.email(),=E6=94=B9=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E5=A4=B9=E5=85=B7=E8=80=8C=E9=9D=9E=E6=94=BE=E5=AE=BD?= =?UTF-8?q?=E7=94=9F=E4=BA=A7=20schema?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr --- packages/contracts/src/__tests__/mail.test.ts | 10 ++++++---- packages/contracts/src/mail.ts | 9 ++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/contracts/src/__tests__/mail.test.ts b/packages/contracts/src/__tests__/mail.test.ts index 22a4d78..ccef494 100644 --- a/packages/contracts/src/__tests__/mail.test.ts +++ b/packages/contracts/src/__tests__/mail.test.ts @@ -37,9 +37,11 @@ describe('mailAccountInputSchema', () => { expect(mailAccountInputSchema.safeParse({ kind: 'wecom', name: 'x', settings: {} }).success).toBe(false); }); - it('轮询间隔下限 10 秒;地址必须是邮箱;kind 只认两种', () => { - expect(mailAccountInputSchema.safeParse({ kind: 'imap-smtp', name: 'a', address: 'a@b.c', pollIntervalSec: 5 }).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']); }); @@ -55,10 +57,10 @@ describe('mailAccountViewSchema', () => { describe('mailSendInputSchema', () => { it('收件人至少一个、主题必填;附件默认空数组;requestedBy 必带 kind', () => { - const ok = mailSendInputSchema.safeParse({ to: ['a@b.c'], subject: 'hi', text: 'x', requestedBy: { kind: 'ops' } }); + 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.c'], subject: 'hi', text: 'x' }).success).toBe(false); + expect(mailSendInputSchema.safeParse({ to: ['a@b.com'], subject: 'hi', text: 'x' }).success).toBe(false); }); }); diff --git a/packages/contracts/src/mail.ts b/packages/contracts/src/mail.ts index 1231f82..f1a552b 100644 --- a/packages/contracts/src/mail.ts +++ b/packages/contracts/src/mail.ts @@ -55,8 +55,7 @@ const accountCommon = { export const mailAccountInputSchema = z.discriminatedUnion('kind', [ z.object({ kind: z.literal('imap-smtp'), - /** zod 默认 email 正则要求顶级域至少两个字母(`.c` 会被拒);邮箱地址理应更宽 —— 改用 html5Email 正则 */ - address: z.email({ pattern: z.regexes.html5Email }), + address: z.email(), ...accountCommon, settings: imapSmtpSettingsSchema.prefault({}), /** 只写不读:缺省 = 不改(与 LLM 密钥同款) */ @@ -179,9 +178,9 @@ export const mailSendInputSchema = z.object({ accountId: z.string().optional(), /** 指定发件地址(channel 用):必须对上一个已启用账号,不静默回落 */ from: z.string().optional(), - to: z.array(z.email({ pattern: z.regexes.html5Email })).min(1), - cc: z.array(z.email({ pattern: z.regexes.html5Email })).default([]), - bcc: z.array(z.email({ pattern: z.regexes.html5Email })).default([]), + to: z.array(z.email()).min(1), + cc: z.array(z.email()).default([]), + bcc: z.array(z.email()).default([]), subject: z.string().min(1), text: z.string().optional(), html: z.string().optional(),