diff --git a/src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx b/src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx index cfdb29b72..c2d2fc7c5 100644 --- a/src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx +++ b/src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx @@ -103,7 +103,8 @@ export function EditKeyForm({ keyData, user, isAdmin = false, onSuccess }: EditK try { const res = await editKey(keyData.id, { name: data.name, - expiresAt: data.expiresAt || undefined, + // 重要:清除到期时间时用空字符串表达,避免 undefined 在 Server Action 序列化时被丢弃 + expiresAt: data.expiresAt ?? "", canLoginWebUi: data.canLoginWebUi, cacheTtlPreference: data.cacheTtlPreference, limit5hUsd: data.limit5hUsd, diff --git a/src/app/[locale]/dashboard/_components/user/unified-edit-dialog.tsx b/src/app/[locale]/dashboard/_components/user/unified-edit-dialog.tsx index b4ee322e0..05947ae7f 100644 --- a/src/app/[locale]/dashboard/_components/user/unified-edit-dialog.tsx +++ b/src/app/[locale]/dashboard/_components/user/unified-edit-dialog.tsx @@ -442,7 +442,8 @@ function UnifiedEditDialogInner({ const keyRes = await addKey({ userId: user.id, name: key.name, - expiresAt: key.expiresAt || undefined, + // 重要:清除到期时间时用空字符串表达,避免 undefined 在 Server Action 序列化时被丢弃 + expiresAt: key.expiresAt ?? "", isEnabled: key.isEnabled, canLoginWebUi: key.canLoginWebUi, providerGroup: normalizeProviderGroup(key.providerGroup), @@ -466,7 +467,8 @@ function UnifiedEditDialogInner({ // Existing key - edit it const keyRes = await editKey(key.id, { name: key.name, - expiresAt: key.expiresAt || undefined, + // 重要:清除到期时间时用空字符串表达,避免 undefined 在 Server Action 序列化时被丢弃 + expiresAt: key.expiresAt ?? "", canLoginWebUi: key.canLoginWebUi, isEnabled: key.isEnabled, providerGroup: normalizeProviderGroup(key.providerGroup), diff --git a/tests/unit/actions/keys-edit-key-expires-at-clear.test.ts b/tests/unit/actions/keys-edit-key-expires-at-clear.test.ts index 9a71a34b1..e483df6eb 100644 --- a/tests/unit/actions/keys-edit-key-expires-at-clear.test.ts +++ b/tests/unit/actions/keys-edit-key-expires-at-clear.test.ts @@ -120,6 +120,21 @@ describe("editKey: expiresAt 清除/不更新语义", () => { ); }); + test('携带 expiresAt="" 时应清除 expires_at(写入 null)', async () => { + const { editKey } = await import("@/actions/keys"); + + const res = await editKey(1, { name: "k2", expiresAt: "" }); + + expect(res.ok).toBe(true); + expect(updateKeyMock).toHaveBeenCalledTimes(1); + expect(updateKeyMock).toHaveBeenCalledWith( + 1, + expect.objectContaining({ + expires_at: null, + }) + ); + }); + test("携带 expiresAt=YYYY-MM-DD 时应写入对应 Date", async () => { const { editKey } = await import("@/actions/keys"); diff --git a/tests/unit/dashboard/edit-key-form-expiry-clear-ui.test.tsx b/tests/unit/dashboard/edit-key-form-expiry-clear-ui.test.tsx index bea163770..b40f36b95 100644 --- a/tests/unit/dashboard/edit-key-form-expiry-clear-ui.test.tsx +++ b/tests/unit/dashboard/edit-key-form-expiry-clear-ui.test.tsx @@ -129,7 +129,7 @@ describe("EditKeyForm: 清除 expiresAt 后应携带 expiresAt 字段提交( expect(keysActionMocks.editKey).toHaveBeenCalledTimes(1); const [, payload] = keysActionMocks.editKey.mock.calls[0] as [number, any]; - // 关键点:必须显式携带 expiresAt 字段(即使为 undefined),后端才会识别为“清除” + // 关键点:必须显式携带 expiresAt 字段(清除时通常为 ""),后端才会识别为“清除” expect(Object.hasOwn(payload, "expiresAt")).toBe(true); unmount();