diff --git a/frontend/src/schemas/protocols/inbound/tunnel.ts b/frontend/src/schemas/protocols/inbound/tunnel.ts index 5ce839ad5..d1a72de85 100644 --- a/frontend/src/schemas/protocols/inbound/tunnel.ts +++ b/frontend/src/schemas/protocols/inbound/tunnel.ts @@ -11,7 +11,12 @@ export type TunnelNetwork = z.infer; // with arr=false. export const TunnelInboundSettingsSchema = z.object({ rewriteAddress: z.string().optional(), - rewritePort: PortSchema.optional(), + // AntD InputNumber writes null when cleared; accept it and collapse to + // undefined so the field is omitted from the payload instead of crashing + // validation with "Invalid input" (issue #5516). The trailing .optional() + // keeps the key optional in the inferred type (a bare .transform() would + // make it required). + rewritePort: PortSchema.nullable().transform((v) => v ?? undefined).optional(), portMap: z.record(z.string(), z.string()).default({}), allowedNetwork: TunnelNetworkSchema.default('tcp,udp'), followRedirect: z.boolean().default(false), diff --git a/frontend/src/test/tunnel-rewriteport.test.ts b/frontend/src/test/tunnel-rewriteport.test.ts new file mode 100644 index 000000000..8e4afd9d5 --- /dev/null +++ b/frontend/src/test/tunnel-rewriteport.test.ts @@ -0,0 +1,26 @@ +import { describe, expect, it } from 'vitest'; + +import { TunnelInboundSettingsSchema } from '@/schemas/protocols/inbound/tunnel'; + +// Regression for issue #5516: AntD InputNumber writes null when the Rewrite +// port field is cleared, which used to crash validation with "Invalid input". +describe('TunnelInboundSettingsSchema rewritePort', () => { + it('accepts null (cleared field) and omits the port', () => { + const parsed = TunnelInboundSettingsSchema.parse({ rewritePort: null }); + expect(parsed.rewritePort).toBeUndefined(); + }); + + it('accepts a missing field', () => { + const parsed = TunnelInboundSettingsSchema.parse({}); + expect(parsed.rewritePort).toBeUndefined(); + }); + + it('preserves a valid port', () => { + const parsed = TunnelInboundSettingsSchema.parse({ rewritePort: 8443 }); + expect(parsed.rewritePort).toBe(8443); + }); + + it('still rejects out-of-range ports', () => { + expect(() => TunnelInboundSettingsSchema.parse({ rewritePort: 70000 })).toThrow(); + }); +});