diff --git a/src/cli/index.ts b/src/cli/index.ts index a08b64d..e32067f 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -997,7 +997,7 @@ program program .command("traces") .description("List audit trail entries") - .option("-s, --server ", "Filter by server") + .option("-s, --server ", "Filter by server ID, partial ID, or slug") .option("-a, --agent ", "Filter by agent") .option("-l, --limit ", `Limit rows shown (default: ${DEFAULT_LIST_LIMIT}; JSON keeps legacy ${LEGACY_JSON_TRACE_LIMIT})`) .option("--cursor ", "Zero-based row offset for pagination") @@ -1012,7 +1012,11 @@ program if (opts.agent) { fetched = listTracesByAgent(opts.agent, cursor + limit + (json ? 0 : 1), db); } else { - fetched = listTraces(opts.server, undefined, cursor + limit + (json ? 0 : 1), db); + // traces.server_id always holds the full UUID, so an unresolved slug or the + // 8-char ID this table renders would match nothing and print an empty result + // at rc=0 — indistinguishable from a server with no traces. + const serverId = opts.server ? resolveServerOrExit(opts.server, db).id : undefined; + fetched = listTraces(serverId, undefined, cursor + limit + (json ? 0 : 1), db); } const traces = fetched.slice(cursor, cursor + limit); if (json) { diff --git a/test/cli-integration.test.ts b/test/cli-integration.test.ts index a9854c9..5ea34c8 100644 --- a/test/cli-integration.test.ts +++ b/test/cli-integration.test.ts @@ -259,6 +259,44 @@ describe("CLI integration tests", () => { } }); + test("traces --server resolves full ID, short ID, slug, and name, and rejects unknown identifiers", async () => { + const { dbFlag, cleanup } = withTmpDb(); + try { + await run(`${CLI} ${dbFlag} servers:add -n "Alpha Server" --slug alpha-srv`); + await run(`${CLI} ${dbFlag} servers:add -n "Beta Server" --slug beta-srv`); + await run(`${CLI} ${dbFlag} trace:add --server alpha-srv --event "alpha.event"`); + await run(`${CLI} ${dbFlag} trace:add --server beta-srv --event "beta.event"`); + + const { stdout: allJson } = await run(`${CLI} ${dbFlag} traces --json`); + const all = JSON.parse(allJson) as { event: string; server_id: string }[]; + expect(all.map(t => t.event).sort()).toEqual(["alpha.event", "beta.event"]); + const alphaId = all.find(t => t.event === "alpha.event")!.server_id; + + const events = async (identifier: string) => { + const { stdout } = await run(`${CLI} ${dbFlag} traces --server ${identifier} --json`); + return (JSON.parse(stdout) as { event: string }[]).map(t => t.event); + }; + + // Positive control: the full UUID already resolved before this fix, so a + // failure here means the fixture or the harness is wrong, not the filter. + expect(await events(alphaId)).toEqual(["alpha.event"]); + + // The short ID is the value the traces table itself renders in its SERVER column. + expect(await events(alphaId.slice(0, 8))).toEqual(["alpha.event"]); + + expect(await events("alpha-srv")).toEqual(["alpha.event"]); + expect(await events('"Beta Server"')).toEqual(["beta.event"]); + + // An unknown identifier must fail loudly. Printing an empty table at rc=0 is + // indistinguishable from a real server that genuinely has no traces. + const unknown = await runExpectFailure(`${CLI} ${dbFlag} traces --server zzz-not-a-server-zzz`); + expect(unknown.stderr).toContain("Server not found"); + expect(unknown.code).not.toBe(0); + } finally { + cleanup(); + } + }); + test("list commands default to compact paginated output while JSON can stay complete", async () => { const { dbFlag, cleanup } = withTmpDb(); try {