mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-05-12 01:59:40 +08:00
249 lines
8.9 KiB
TypeScript
249 lines
8.9 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
||
|
||
import {
|
||
formatJVMDiagnosticChunkText,
|
||
formatJVMDiagnosticChunksForDisplay,
|
||
formatJVMDiagnosticCommandTypeLabel,
|
||
formatJVMDiagnosticPhaseLabel,
|
||
formatJVMDiagnosticRiskLabel,
|
||
formatJVMDiagnosticSourceLabel,
|
||
formatJVMDiagnosticTransportLabel,
|
||
groupJVMDiagnosticPresets,
|
||
redactJVMDiagnosticOutput,
|
||
resolveJVMDiagnosticRiskColor,
|
||
} from "./jvmDiagnosticPresentation";
|
||
|
||
describe("jvmDiagnosticPresentation", () => {
|
||
it("groups presets by category in a stable order", () => {
|
||
const groups = groupJVMDiagnosticPresets();
|
||
expect(groups.map((group) => group.label)).toEqual([
|
||
"观察类命令",
|
||
"跟踪类命令",
|
||
"高风险命令",
|
||
]);
|
||
expect(groups[0].items.some((item) => item.label === "thread")).toBe(true);
|
||
});
|
||
|
||
it("formats chunk text with localized phase prefix when content exists", () => {
|
||
expect(
|
||
formatJVMDiagnosticChunkText({
|
||
sessionId: "sess-1",
|
||
phase: "running",
|
||
content: "thread -n 5",
|
||
}),
|
||
).toBe("执行中:thread -n 5");
|
||
});
|
||
|
||
it("redacts sensitive values in diagnostic output chunks", () => {
|
||
const text = formatJVMDiagnosticChunkText({
|
||
sessionId: "sess-1",
|
||
phase: "running",
|
||
content:
|
||
"password=secret-token\napiKey: api-key-secret\naccessToken = bearer-secret\nPRIVATE_KEY=-----BEGIN PRIVATE KEY-----raw-key",
|
||
});
|
||
|
||
expect(text).toContain("password=********");
|
||
expect(text).toContain("apiKey: ********");
|
||
expect(text).toContain("accessToken = ********");
|
||
expect(text).toContain("PRIVATE_KEY=********");
|
||
expect(text).not.toContain("secret-token");
|
||
expect(text).not.toContain("api-key-secret");
|
||
expect(text).not.toContain("bearer-secret");
|
||
expect(text).not.toContain("raw-key");
|
||
});
|
||
|
||
it("redacts JSON, environment, separator and partial PEM sensitive output", () => {
|
||
const text = redactJVMDiagnosticOutput([
|
||
'{"password":"json-secret","api_key":"api-json-secret","accessToken":"access-json-secret"}',
|
||
"DB_PASSWORD=hunter2",
|
||
"SPRING_DATASOURCE_PASSWORD=spring-secret",
|
||
"AWS_SECRET_ACCESS_KEY=aws-secret",
|
||
"api-key: kebab-secret",
|
||
"api key = spaced-secret",
|
||
"private.key: dot-secret",
|
||
"refresh_token=refresh-secret",
|
||
"secret=foo;bar",
|
||
"PRIVATE_KEY=-----BEGIN PRIVATE KEY-----\nraw-key-line",
|
||
].join("\n"));
|
||
|
||
expect(text).toContain('"password":"********"');
|
||
expect(text).toContain('"api_key":"********"');
|
||
expect(text).toContain('"accessToken":"********"');
|
||
expect(text).toContain("DB_PASSWORD=********");
|
||
expect(text).toContain("SPRING_DATASOURCE_PASSWORD=********");
|
||
expect(text).toContain("AWS_SECRET_ACCESS_KEY=********");
|
||
expect(text).toContain("api-key: ********");
|
||
expect(text).toContain("api key = ********");
|
||
expect(text).toContain("private.key: ********");
|
||
expect(text).toContain("refresh_token=********");
|
||
expect(text).toContain("secret=********");
|
||
expect(text).toContain("PRIVATE_KEY=********");
|
||
expect(text).not.toContain("json-secret");
|
||
expect(text).not.toContain("api-json-secret");
|
||
expect(text).not.toContain("access-json-secret");
|
||
expect(text).not.toContain("hunter2");
|
||
expect(text).not.toContain("spring-secret");
|
||
expect(text).not.toContain("aws-secret");
|
||
expect(text).not.toContain("kebab-secret");
|
||
expect(text).not.toContain("spaced-secret");
|
||
expect(text).not.toContain("dot-secret");
|
||
expect(text).not.toContain("refresh-secret");
|
||
expect(text).not.toContain("foo;bar");
|
||
expect(text).not.toContain("raw-key-line");
|
||
});
|
||
|
||
it("redacts PEM continuation across diagnostic chunks", () => {
|
||
const texts = formatJVMDiagnosticChunksForDisplay([
|
||
{
|
||
sessionId: "sess-1",
|
||
phase: "running",
|
||
content: "PRIVATE_KEY=-----BEGIN PRIVATE KEY-----\nabc123",
|
||
},
|
||
{
|
||
sessionId: "sess-1",
|
||
phase: "running",
|
||
content: "def456\n-----END PRIVATE KEY-----",
|
||
},
|
||
{
|
||
sessionId: "sess-1",
|
||
phase: "running",
|
||
content: "thread_name=main",
|
||
},
|
||
]);
|
||
|
||
expect(texts.join("\n")).not.toContain("abc123");
|
||
expect(texts.join("\n")).not.toContain("def456");
|
||
expect(texts.join("\n")).not.toContain("PRIVATE KEY");
|
||
expect(texts[2]).toContain("thread_name=main");
|
||
});
|
||
|
||
it("redacts PEM begin marker split across diagnostic chunks", () => {
|
||
const texts = formatJVMDiagnosticChunksForDisplay([
|
||
{
|
||
sessionId: "sess-1",
|
||
phase: "running",
|
||
content: "PRIVATE_KEY=-----BEGIN PRIV",
|
||
},
|
||
{
|
||
sessionId: "sess-1",
|
||
phase: "running",
|
||
content: "ATE KEY-----\nabc123\n-----END PRIVATE KEY-----",
|
||
},
|
||
{
|
||
sessionId: "sess-1",
|
||
phase: "running",
|
||
content: "thread_name=main",
|
||
},
|
||
]);
|
||
|
||
expect(texts.join("\n")).not.toContain("BEGIN PRIV");
|
||
expect(texts.join("\n")).not.toContain("ATE KEY");
|
||
expect(texts.join("\n")).not.toContain("abc123");
|
||
expect(texts[2]).toContain("thread_name=main");
|
||
});
|
||
|
||
it("redacts algorithm-prefixed PEM begin marker split across chunks", () => {
|
||
const texts = formatJVMDiagnosticChunksForDisplay([
|
||
{
|
||
sessionId: "sess-1",
|
||
phase: "running",
|
||
content: "-----BEGIN RSA PRIV",
|
||
},
|
||
{
|
||
sessionId: "sess-1",
|
||
phase: "running",
|
||
content: "ATE KEY-----\nabc123\n-----END RSA PRIVATE KEY-----",
|
||
},
|
||
{
|
||
sessionId: "sess-1",
|
||
phase: "running",
|
||
content: "thread_name=main",
|
||
},
|
||
]);
|
||
|
||
expect(texts.join("\n")).not.toContain("RSA PRIV");
|
||
expect(texts.join("\n")).not.toContain("ATE KEY");
|
||
expect(texts.join("\n")).not.toContain("abc123");
|
||
expect(texts[2]).toContain("thread_name=main");
|
||
});
|
||
|
||
it("redacts algorithm-prefixed PEM markers split after the algorithm and inside key labels", () => {
|
||
const cases = [
|
||
["-----BEGIN RSA", " PRIVATE KEY-----\nabc123\n-----END RSA PRIVATE KEY-----"],
|
||
["-----BEGIN RSA PRIVATE K", "EY-----\nabc123\n-----END RSA PRIVATE KEY-----"],
|
||
["-----BEGIN OPENSSH", " PRIVATE KEY-----\nabc123\n-----END OPENSSH PRIVATE KEY-----"],
|
||
["-----BEGIN EC PRIVATE KE", "Y-----\nabc123\n-----END EC PRIVATE KEY-----"],
|
||
];
|
||
|
||
for (const [firstChunk, secondChunk] of cases) {
|
||
const texts = formatJVMDiagnosticChunksForDisplay([
|
||
{
|
||
sessionId: "sess-1",
|
||
phase: "running",
|
||
content: firstChunk,
|
||
},
|
||
{
|
||
sessionId: "sess-1",
|
||
phase: "running",
|
||
content: secondChunk,
|
||
},
|
||
]);
|
||
|
||
expect(texts.join("\n")).not.toContain("PRIVATE K");
|
||
expect(texts.join("\n")).not.toContain("EY-----");
|
||
expect(texts.join("\n")).not.toContain("abc123");
|
||
}
|
||
});
|
||
|
||
it("redacts JSON scalar values and URL query parameters", () => {
|
||
const text = redactJVMDiagnosticOutput(
|
||
'{"password":123456,"token":true,"credential":null}\nhttps://svc.local/callback?access_token=url-secret&x=1&api_key=query-secret',
|
||
);
|
||
|
||
expect(text).toContain('"password":********');
|
||
expect(text).toContain('"token":********');
|
||
expect(text).toContain('"credential":********');
|
||
expect(text).toContain("access_token=********");
|
||
expect(text).toContain("api_key=********");
|
||
expect(text).not.toContain("123456");
|
||
expect(text).not.toContain("true");
|
||
expect(text).not.toContain("url-secret");
|
||
expect(text).not.toContain("query-secret");
|
||
});
|
||
|
||
it("redacts authorization values across text, JSON and query parameters", () => {
|
||
const text = redactJVMDiagnosticOutput(
|
||
'Authorization: Bearer header-secret\n{"authorization":"Bearer json-secret"}\nhttps://svc.local/callback?authorization=Bearer%20query-secret',
|
||
);
|
||
|
||
expect(text).toContain("Authorization: ********");
|
||
expect(text).toContain('"authorization":"********"');
|
||
expect(text).toContain("authorization=********");
|
||
expect(text).not.toContain("header-secret");
|
||
expect(text).not.toContain("json-secret");
|
||
expect(text).not.toContain("query-secret");
|
||
});
|
||
|
||
it("keeps non-sensitive diagnostic output unchanged", () => {
|
||
expect(
|
||
redactJVMDiagnosticOutput(
|
||
"thread_name=main\nmethod: com.foo.OrderService.submit\ncost=42ms",
|
||
),
|
||
).toBe("thread_name=main\nmethod: com.foo.OrderService.submit\ncost=42ms");
|
||
});
|
||
|
||
it("localizes diagnostic status, transport, risk and source labels", () => {
|
||
expect(formatJVMDiagnosticPhaseLabel("completed")).toBe("已完成");
|
||
expect(formatJVMDiagnosticTransportLabel("arthas-tunnel")).toBe("Arthas Tunnel");
|
||
expect(formatJVMDiagnosticRiskLabel("high")).toBe("高风险");
|
||
expect(formatJVMDiagnosticCommandTypeLabel("trace")).toBe("跟踪类");
|
||
expect(formatJVMDiagnosticSourceLabel("ai-plan")).toBe("AI 计划");
|
||
});
|
||
|
||
it("maps risk levels to tag colors", () => {
|
||
expect(resolveJVMDiagnosticRiskColor("low")).toBe("green");
|
||
expect(resolveJVMDiagnosticRiskColor("medium")).toBe("gold");
|
||
expect(resolveJVMDiagnosticRiskColor("high")).toBe("red");
|
||
});
|
||
});
|