Files
opsxdb/src/redis.test.ts
T

120 lines
3.7 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
buildRedisKeyTree,
createKeyCommand,
isDangerousRedis,
quoteRedisArg,
redisCmd,
redisLeafLabel,
removeRedisKeyFromTree,
splitRedisCommands,
tryPrettyJson,
tsLabel,
} from "./redis";
describe("buildRedisKeyTree", () => {
it("按 : 编文件夹,叶子保留全名", () => {
const tree = buildRedisKeyTree(
[
{ name: "CEX:FOLLOW:FOLLOWERS:FUTURE:97865651", type: "hash", ttl: -1 },
{ name: "CEX:FOLLOW:TRADERS:1", type: "hash", ttl: -1 },
{ name: "LANG", type: "string", ttl: 12 },
],
":",
);
expect(tree.map((n) => n.name)).toEqual(["CEX", "LANG"]);
const cex = tree[0];
expect(cex.kind).toBe("folder");
expect(cex.count).toBe(2);
expect(cex.fullName).toBe("CEX:");
const follow = cex.children?.[0];
expect(follow?.name).toBe("FOLLOW");
const leaves = (follow?.children ?? []).flatMap((n) =>
n.kind === "folder" ? n.children ?? [] : [n],
);
expect(leaves.some((n) => n.name === "CEX:FOLLOW:TRADERS:1")).toBe(true);
});
it("无分隔符时保持扁平", () => {
const tree = buildRedisKeyTree(
[
{ name: "alpha", type: "string", ttl: -1 },
{ name: "beta", type: "hash", ttl: 3 },
],
":",
);
expect(tree.every((n) => n.kind === "key")).toBe(true);
expect(tree.map((n) => n.name)).toEqual(["alpha", "beta"]);
});
it("删除键后收起空文件夹", () => {
const tree = buildRedisKeyTree(
[
{ name: "a:b:c", type: "string", ttl: -1 },
{ name: "a:x", type: "string", ttl: -1 },
],
":",
);
const next = removeRedisKeyFromTree(tree, "a:b:c");
expect(next).toHaveLength(1);
expect(next[0].kind).toBe("folder");
expect(next[0].count).toBe(1);
expect(next[0].children?.[0].name).toBe("a:x");
});
});
describe("quote / cmd", () => {
it("空白与引号转义", () => {
expect(quoteRedisArg("abc")).toBe("abc");
expect(quoteRedisArg("a b")).toBe('"a b"');
expect(quoteRedisArg('say "hi"')).toBe('"say \\"hi\\""');
expect(redisCmd("SET", "k", "v w")).toBe('SET k "v w"');
});
it("新建键命令", () => {
expect(createKeyCommand("string", "demo", "x")).toBe("SET demo x");
expect(createKeyCommand("hash", "h", "v")).toBe("HSET h field v");
});
});
describe("splitRedisCommands", () => {
it("按行切,跳过空行和 # 注释", () => {
const parts = splitRedisCommands("GET a\n\n# skip\nHGETALL b");
expect(parts.map((p) => p.text)).toEqual(["GET a", "HGETALL b"]);
});
it("不把分号当分隔", () => {
const parts = splitRedisCommands('SET k "a;b"');
expect(parts).toHaveLength(1);
expect(parts[0].text).toBe('SET k "a;b"');
});
});
describe("misc", () => {
it("危险命令", () => {
expect(isDangerousRedis("FLUSHALL")).toBe(true);
expect(isDangerousRedis("GET k")).toBe(false);
expect(isDangerousRedis("keys *")).toBe(true);
});
it("JSON 美化", () => {
expect(tryPrettyJson('{"a":1}')).toBe('{\n "a": 1\n}');
expect(tryPrettyJson("not-json")).toBeNull();
});
it("leaf label", () => {
expect(redisLeafLabel("a:b:c", ":")).toBe("c");
expect(redisLeafLabel("plain", ":")).toBe("plain");
});
});
describe("tsLabel", () => {
it("毫秒时间戳给可读时间", () => {
expect(tsLabel(1782230400000)).toMatch(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/);
});
it("秒时间戳给可读时间", () => {
expect(tsLabel(1782230400)).toMatch(/^\d{4}-/);
});
it("普通数字/非法值不误判", () => {
expect(tsLabel(10)).toBeNull();
expect(tsLabel(77777777777777)).toBeNull();
expect(tsLabel(Number.NaN)).toBeNull();
});
});