Browse Source

fix(config): normalize comment mail smtp global values before validation

Apply trim/blank-to-empty normalization for comment email/smtp global keys in setGlobalConfigValue, and replace registry test casts with KnownConfigKey-safe assertions plus normalization coverage.

Made-with: Cursor
main
npmrun 3 weeks ago
parent
commit
c6349c65f5
  1. 5
      server/service/config/index.ts
  2. 18
      server/service/config/normalization.ts
  3. 93
      server/service/config/registry.test.ts

5
server/service/config/index.ts

@ -13,6 +13,7 @@ import {
serializeConfigValue,
validateConfigValue,
} from "./registry";
import { normalizeGlobalConfigValue } from "./normalization";
export class ConfigKeyNotFoundError extends Error {
code = "CONFIG_KEY_NOT_FOUND";
@ -108,9 +109,7 @@ export async function setGlobalConfigValue<K extends KnownConfigKey>(key: K, val
if (scope === "user") {
throw new ConfigScopeInvalidError(`配置项 ${key} 不支持全局写入`);
}
if (key === "siteName" && typeof value === "string") {
value = value.trim();
}
value = normalizeGlobalConfigValue(key, value);
if (!validateConfigValue(key, value)) {
throw new ConfigValueInvalidError(`配置项 ${key} 的值不合法`);
}

18
server/service/config/normalization.ts

@ -0,0 +1,18 @@
import { KnownConfigKey } from "./registry";
const TRIMMABLE_GLOBAL_CONFIG_KEYS = new Set<KnownConfigKey>([
"commentMailFromEmail",
"commentSmtpHost",
"commentSmtpUser",
"commentSmtpPass",
]);
export function normalizeGlobalConfigValue<K extends KnownConfigKey>(key: K, value: unknown): unknown {
if (typeof value !== "string") {
return value;
}
if (key === "siteName" || TRIMMABLE_GLOBAL_CONFIG_KEYS.has(key)) {
return value.trim();
}
return value;
}

93
server/service/config/registry.test.ts

@ -1,84 +1,69 @@
import { describe, expect, test } from "bun:test";
import { getConfigDefinition, validateConfigValue } from "./registry";
import { getConfigDefinition, KnownConfigKey, validateConfigValue } from "./registry";
import { normalizeGlobalConfigValue } from "./normalization";
describe("comment email config validation", () => {
test("accepts an empty commentMailFromEmail", () => {
expect(validateConfigValue("commentMailFromEmail" as never, "")).toBe(true);
expect(validateConfigValue("commentMailFromEmail", "")).toBe(true);
});
test("accepts a valid commentMailFromEmail", () => {
expect(validateConfigValue("commentMailFromEmail" as never, "noreply@example.com")).toBe(true);
expect(validateConfigValue("commentMailFromEmail", "noreply@example.com")).toBe(true);
});
test("rejects an invalid commentMailFromEmail", () => {
expect(validateConfigValue("commentMailFromEmail" as never, "invalid-email")).toBe(false);
expect(validateConfigValue("commentMailFromEmail", "invalid-email")).toBe(false);
});
test("validates commentNotifyEnabled as boolean", () => {
expect(validateConfigValue("commentNotifyEnabled" as never, true)).toBe(true);
expect(validateConfigValue("commentNotifyEnabled" as never, false)).toBe(true);
expect(validateConfigValue("commentNotifyEnabled" as never, "true")).toBe(false);
expect(validateConfigValue("commentNotifyEnabled", true)).toBe(true);
expect(validateConfigValue("commentNotifyEnabled", false)).toBe(true);
expect(validateConfigValue("commentNotifyEnabled", "true")).toBe(false);
});
test("enforces commentSmtpPort boundaries", () => {
expect(validateConfigValue("commentSmtpPort" as never, 1)).toBe(true);
expect(validateConfigValue("commentSmtpPort" as never, 65535)).toBe(true);
expect(validateConfigValue("commentSmtpPort" as never, 0)).toBe(false);
expect(validateConfigValue("commentSmtpPort" as never, 65536)).toBe(false);
expect(validateConfigValue("commentSmtpPort" as never, 465)).toBe(true);
expect(validateConfigValue("commentSmtpPort", 1)).toBe(true);
expect(validateConfigValue("commentSmtpPort", 65535)).toBe(true);
expect(validateConfigValue("commentSmtpPort", 0)).toBe(false);
expect(validateConfigValue("commentSmtpPort", 65536)).toBe(false);
expect(validateConfigValue("commentSmtpPort", 465)).toBe(true);
});
test("keeps metadata consistent for new global comment config keys", () => {
expect(getConfigDefinition("commentEmailNotifyEnabled" as never)).toMatchObject({
scope: "global",
valueType: "boolean",
defaultValue: false,
userOverridable: false,
});
expect(getConfigDefinition("commentMailFromEmail" as never)).toMatchObject({
scope: "global",
valueType: "string",
defaultValue: "",
userOverridable: false,
});
expect(getConfigDefinition("commentSmtpHost" as never)).toMatchObject({
scope: "global",
valueType: "string",
defaultValue: "",
userOverridable: false,
});
expect(getConfigDefinition("commentSmtpPort" as never)).toMatchObject({
scope: "global",
valueType: "number",
defaultValue: 465,
userOverridable: false,
});
expect(getConfigDefinition("commentSmtpSecure" as never)).toMatchObject({
scope: "global",
valueType: "boolean",
defaultValue: true,
userOverridable: false,
});
expect(getConfigDefinition("commentSmtpUser" as never)).toMatchObject({
scope: "global",
valueType: "string",
defaultValue: "",
userOverridable: false,
});
expect(getConfigDefinition("commentSmtpPass" as never)).toMatchObject({
scope: "global",
valueType: "string",
defaultValue: "",
userOverridable: false,
const expected = [
["commentEmailNotifyEnabled", "global", "boolean", false, false],
["commentMailFromEmail", "global", "string", "", false],
["commentSmtpHost", "global", "string", "", false],
["commentSmtpPort", "global", "number", 465, false],
["commentSmtpSecure", "global", "boolean", true, false],
["commentSmtpUser", "global", "string", "", false],
["commentSmtpPass", "global", "string", "", false],
] as const satisfies readonly [KnownConfigKey, "global", "string" | "number" | "boolean", string | number | boolean, boolean][];
for (const [key, scope, valueType, defaultValue, userOverridable] of expected) {
expect(getConfigDefinition(key)).toMatchObject({
scope,
valueType,
defaultValue,
userOverridable,
});
}
});
test("keeps metadata consistent for commentNotifyEnabled", () => {
expect(getConfigDefinition("commentNotifyEnabled" as never)).toMatchObject({
expect(getConfigDefinition("commentNotifyEnabled")).toMatchObject({
scope: "both",
valueType: "boolean",
defaultValue: true,
userOverridable: true,
});
});
test("normalizes comment email and smtp strings by trimming", () => {
expect(normalizeGlobalConfigValue("commentMailFromEmail", " noreply@example.com ")).toBe("noreply@example.com");
expect(normalizeGlobalConfigValue("commentMailFromEmail", " ")).toBe("");
expect(normalizeGlobalConfigValue("commentSmtpHost", " smtp.example.com ")).toBe("smtp.example.com");
expect(normalizeGlobalConfigValue("commentSmtpUser", " username ")).toBe("username");
expect(normalizeGlobalConfigValue("commentSmtpPass", " password ")).toBe("password");
});
});

Loading…
Cancel
Save