Files
fathah--hermes-desktop/tests/api-server-key-source.test.ts
2026-07-13 13:36:53 +08:00

149 lines
4.4 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
CANONICAL_API_KEY_SOURCES,
appendConfigFixLog,
maskKey,
resolveApiServerKeyWithSource,
} from "../src/main/config";
/**
* Source-aware variant of the precedence resolver. The plain
* `resolveApiServerKey` test in api-server-key-resolution.test.ts pins
* the precedence policy; this one pins the source tag, which the
* migration-on-read and config-health-audit features both depend on.
*/
describe("resolveApiServerKeyWithSource", () => {
const empty = {
configTopLevelProfile: null,
configTopLevelDefault: null,
envProfile: null,
envDefault: null,
apiServerTokenProfile: null,
apiServerTokenDefault: null,
};
it("returns null source when every candidate is empty", () => {
expect(resolveApiServerKeyWithSource(empty)).toEqual({
value: "",
source: null,
});
});
it("tags configTopLevelProfile when only that is set", () => {
expect(
resolveApiServerKeyWithSource({
...empty,
configTopLevelProfile: " sk-cfg-profile ",
}),
).toEqual({ value: "sk-cfg-profile", source: "configTopLevelProfile" });
});
it("tags envProfile when both env profile and api_server.token are set (env wins)", () => {
expect(
resolveApiServerKeyWithSource({
...empty,
envProfile: "sk-env-profile",
apiServerTokenProfile: "sk-token-profile",
}),
).toEqual({ value: "sk-env-profile", source: "envProfile" });
});
it("tags apiServerTokenProfile when only that is set", () => {
expect(
resolveApiServerKeyWithSource({
...empty,
apiServerTokenProfile: "sk-token-profile",
}),
).toEqual({
value: "sk-token-profile",
source: "apiServerTokenProfile",
});
});
it("falls through to apiServerTokenDefault when everything earlier is empty", () => {
expect(
resolveApiServerKeyWithSource({
...empty,
apiServerTokenDefault: "sk-token-default",
}),
).toEqual({
value: "sk-token-default",
source: "apiServerTokenDefault",
});
});
it("preserves the precedence order from resolveApiServerKey", () => {
// All six set — should pick configTopLevelProfile (first in order)
expect(
resolveApiServerKeyWithSource({
configTopLevelProfile: "a",
configTopLevelDefault: "b",
envProfile: "c",
envDefault: "d",
apiServerTokenProfile: "e",
apiServerTokenDefault: "f",
}),
).toEqual({ value: "a", source: "configTopLevelProfile" });
});
it("treats whitespace-only candidates as empty", () => {
expect(
resolveApiServerKeyWithSource({
...empty,
configTopLevelProfile: " ",
envProfile: "sk-real",
}),
).toEqual({ value: "sk-real", source: "envProfile" });
});
});
describe("CANONICAL_API_KEY_SOURCES", () => {
it("contains only the .env-backed sources", () => {
expect(CANONICAL_API_KEY_SOURCES.has("envProfile")).toBe(true);
expect(CANONICAL_API_KEY_SOURCES.has("envDefault")).toBe(true);
expect(CANONICAL_API_KEY_SOURCES.has("configTopLevelProfile")).toBe(false);
expect(CANONICAL_API_KEY_SOURCES.has("configTopLevelDefault")).toBe(false);
expect(CANONICAL_API_KEY_SOURCES.has("apiServerTokenProfile")).toBe(false);
expect(CANONICAL_API_KEY_SOURCES.has("apiServerTokenDefault")).toBe(false);
});
});
describe("maskKey", () => {
it("returns empty string for empty input", () => {
expect(maskKey("")).toBe("");
});
it("returns generic mask for very short values", () => {
expect(maskKey("short")).toBe("***");
expect(maskKey("sk-12345")).toBe("***");
});
it("keeps first 4 + last 4 for longer values", () => {
expect(maskKey("sk-test-arc-codex-AAAA-BBBB-CCCC")).toBe("sk-t…CCCC");
expect(maskKey("OPENROUTER123456789")).toBe("OPEN…6789");
});
it("does not leak the middle even for medium-length keys", () => {
const masked = maskKey("sk-1234567890");
expect(masked).not.toContain("567");
expect(masked.startsWith("sk-1")).toBe(true);
expect(masked.endsWith("7890")).toBe(true);
});
});
describe("appendConfigFixLog", () => {
it("never throws on bad input (writes are best-effort)", () => {
expect(() =>
appendConfigFixLog({
ts: Date.now(),
issueCode: "TEST_NOOP",
action: "migrate",
from: "configTopLevelProfile",
to: ".env",
valueMasked: "sk-t…CCCC",
}),
).not.toThrow();
});
});