From 5dc0b156bd141c62989fbe67763c6f847c5b8cd4 Mon Sep 17 00:00:00 2001 From: Ding <44717411+ding113@users.noreply.github.com> Date: Sun, 4 Jan 2026 17:37:31 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20Key=20=E6=B8=85=E9=99=A4=E5=88=B0?= =?UTF-8?q?=E6=9C=9F=E6=97=B6=E9=97=B4=E4=BD=BF=E7=94=A8=E7=A9=BA=E5=AD=97?= =?UTF-8?q?=E7=AC=A6=E4=B8=B2=E4=BC=A0=E5=8F=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_components/user/forms/edit-key-form.tsx | 3 ++- .../_components/user/unified-edit-dialog.tsx | 6 ++++-- .../keys-edit-key-expires-at-clear.test.ts | 15 +++++++++++++++ .../edit-key-form-expiry-clear-ui.test.tsx | 2 +- 4 files changed, 22 insertions(+), 4 deletions(-) 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();