fix(redis): recognize OBJECT subcommands

This commit is contained in:
t8y2 2026-07-22 23:11:22 +08:00
parent 08d73ce371
commit 7ca5e76c14
4 changed files with 55 additions and 12 deletions

View File

@ -115,11 +115,13 @@ const RAW_COMMANDS: Record<string, Spec> = {
KEYS: [2, "generic", "blocked"],
MIGRATE: [-6, "generic", "blocked"],
MOVE: [3, "generic", "confirm"],
OBJECT_ENCODING: [3, "generic"],
OBJECT_FREQ: [3, "generic"],
OBJECT_IDLETIME: [3, "generic"],
OBJECT_REFCOUNT: [3, "generic"],
OBJECT_HELP: [2, "generic"],
// Redis exposes OBJECT operations as space-delimited subcommands; keep these
// keys aligned with COMMAND metadata so shared parsing and completion work.
"OBJECT ENCODING": [3, "generic"],
"OBJECT FREQ": [3, "generic"],
"OBJECT IDLETIME": [3, "generic"],
"OBJECT REFCOUNT": [3, "generic"],
"OBJECT HELP": [2, "generic"],
PERSIST: [2, "generic", "confirm"],
PEXPIRE: [-3, "generic", "confirm"],
PEXPIREAT: [-3, "generic", "confirm"],

View File

@ -59,6 +59,25 @@ test("resolveRedisCommandSpec resolves subcommand then main", () => {
assert.equal(main?.group, "string");
});
test("resolveRedisCommandSpec resolves every OBJECT subcommand with Redis arity", () => {
const expectedArities = new Map([
["ENCODING", 3],
["FREQ", 3],
["IDLETIME", 3],
["REFCOUNT", 3],
["HELP", 2],
]);
for (const [subcommand, arity] of expectedArities) {
const spec = resolveRedisCommandSpec(["OBJECT", subcommand]);
assert.ok(spec, `OBJECT ${subcommand} should resolve`);
assert.equal(spec.arity, arity);
assert.equal(spec.group, "generic");
}
assert.equal(resolveRedisCommandSpec(["OBJECT", "UNKNOWN"]), undefined);
});
test("normal writes do not require confirmation but destructive commands do", () => {
assert.equal(resolveRedisCommandSpec(["SET"])?.safety, "write");
assert.equal(resolveRedisCommandSpec(["HSET"])?.safety, "write");

View File

@ -1,12 +1,6 @@
import assert from "node:assert/strict";
import { test } from "vitest";
import {
buildRedisCompletionItems,
getRedisCompletionContext,
getRedisCompletionResultValidFor,
shouldAutoOpenRedisCompletion,
takesKeyArgument,
} from "../../apps/desktop/src/lib/redis/redisCompletion.ts";
import { buildRedisCompletionItems, getRedisCompletionContext, getRedisCompletionResultValidFor, shouldAutoOpenRedisCompletion, takesKeyArgument } from "../../apps/desktop/src/lib/redis/redisCompletion.ts";
function labels(items: { label: string }[]): string[] {
return items.map((item) => item.label);
@ -60,6 +54,16 @@ test("subcommand mode: filters subcommands by prefix", () => {
assert.ok(!names.includes("DESTROY"));
});
test("OBJECT completion uses Redis space-delimited subcommands", () => {
const commandNames = labels(buildRedisCompletionItems("OBJECT", 6));
assert.ok(commandNames.includes("OBJECT"));
assert.ok(!commandNames.some((name) => name.startsWith("OBJECT_")));
const subcommands = labels(buildRedisCompletionItems("OBJECT ", 7));
assert.deepEqual(new Set(subcommands), new Set(["ENCODING", "FREQ", "IDLETIME", "REFCOUNT", "HELP"]));
assert.ok(!subcommands.some((name) => name.includes("_")));
});
test("argument mode: offers key names for key-taking commands", () => {
const items = buildRedisCompletionItems("GET ", 4, { keys: ["user:1", "user:2", "config:db"] });
const names = labels(items);

View File

@ -90,6 +90,24 @@ test("subcommands resolve via MAIN SUB key", () => {
assert.equal(diags.length, 0);
});
test("accepts all Redis OBJECT subcommands", () => {
for (const command of ["OBJECT ENCODING key", "OBJECT FREQ key", "OBJECT IDLETIME key", "OBJECT REFCOUNT key", "OBJECT HELP"]) {
assert.deepEqual(messages(command), [], `${command} should be valid`);
}
});
test("validates OBJECT subcommand arity and rejects unknown subcommands", () => {
for (const command of ["OBJECT ENCODING", "OBJECT FREQ", "OBJECT IDLETIME", "OBJECT REFCOUNT", "OBJECT HELP extra"]) {
const diags = buildRedisSyntaxDiagnostics(command);
assert.equal(diags.length, 1, `${command} should have one diagnostic`);
assert.match(diags[0].message, /Wrong number of arguments for 'OBJECT'/);
}
const unknown = buildRedisSyntaxDiagnostics("OBJECT UNKNOWN key");
assert.equal(unknown.length, 1);
assert.match(unknown[0].message, /Unknown command 'OBJECT'/);
});
test("treats command names case-insensitively", () => {
assert.deepEqual(messages("get foo"), []);
const setDiag = buildRedisSyntaxDiagnostics("Set a b");