From 6da3a68f81e396d61adac2453720b9b680674f7c Mon Sep 17 00:00:00 2001 From: tarunkumartiwary1 Date: Thu, 10 Sep 2026 21:26:42 +0530 Subject: [PATCH] Adding test prune test and error test --- packages/cli/src/commands/errors.test.ts | 122 +++++++++++++++++++++++ packages/cli/src/commands/prune.test.ts | 111 +++++++++++++++++++++ 2 files changed, 233 insertions(+) create mode 100644 packages/cli/src/commands/errors.test.ts create mode 100644 packages/cli/src/commands/prune.test.ts diff --git a/packages/cli/src/commands/errors.test.ts b/packages/cli/src/commands/errors.test.ts new file mode 100644 index 0000000..082331d --- /dev/null +++ b/packages/cli/src/commands/errors.test.ts @@ -0,0 +1,122 @@ +import { describe, it, expect, afterEach, vi } from "vitest"; +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { Command } from "commander"; +import { SqliteStore } from "@obyflow/core"; +import { registerErrorsCommand } from "./errors.js"; + +function buildProgram(): Command { + const program = new Command(); + program.exitOverride(); + registerErrorsCommand(program); + return program; +} + +function seedEvents(dbPath: string): void { + const store = new SqliteStore(dbPath); + const now = Date.now(); + store.insert({ + id: "evt-1", + type: "log", + trace_id: "trace-1", + request_id: null, + service: "checkout-service", + host: null, + container: null, + deployment_id: null, + timestamp: new Date(now).toISOString(), + duration_ms: null, + attributes: {}, + severity: "info", + }); + store.insert({ + id: "evt-2", + type: "error", + trace_id: "trace-2", + request_id: null, + service: "checkout-service", + host: null, + container: null, + deployment_id: null, + timestamp: new Date(now + 1000).toISOString(), + duration_ms: null, + attributes: {}, + severity: "error", + }); + store.insert({ + id: "evt-3", + type: "error", + trace_id: "trace-3", + request_id: null, + service: "billing-service", + host: null, + container: null, + deployment_id: null, + timestamp: new Date(now + 2000).toISOString(), + duration_ms: null, + attributes: {}, + severity: "critical", + }); + store.close(); +} + +describe("obyflow errors", () => { + let dir: string; + + afterEach(() => { + if (dir) rmSync(dir, { recursive: true, force: true }); + }); + + it("lists only error and critical severity events", async () => { + dir = mkdtempSync(join(tmpdir(), "obyflow-cli-errors-")); + const dbPath = join(dir, "test.db"); + seedEvents(dbPath); + + const logSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + const program = buildProgram(); + + await program.parseAsync(["errors", "--db", dbPath], { from: "user" }); + + const output = logSpy.mock.calls.map((call) => call.join(" ")).join("\n"); + expect(output).toContain("checkout-service"); + expect(output).toContain("billing-service"); + expect(output).toContain("2 error(s)"); + logSpy.mockRestore(); + }); + + it("filters by service", async () => { + dir = mkdtempSync(join(tmpdir(), "obyflow-cli-errors-")); + const dbPath = join(dir, "test.db"); + seedEvents(dbPath); + + const logSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + const program = buildProgram(); + + await program.parseAsync( + ["errors", "--db", dbPath, "--service", "billing-service"], + { from: "user" }, + ); + + const output = logSpy.mock.calls.map((call) => call.join(" ")).join("\n"); + expect(output).toContain("billing-service"); + expect(output).not.toContain("checkout-service"); + logSpy.mockRestore(); + }); + + it("renders detail cards when --detail is passed", async () => { + dir = mkdtempSync(join(tmpdir(), "obyflow-cli-errors-")); + const dbPath = join(dir, "test.db"); + seedEvents(dbPath); + + const logSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + const program = buildProgram(); + + await program.parseAsync(["errors", "--db", dbPath, "--detail"], { from: "user" }); + + const output = logSpy.mock.calls.map((call) => call.join(" ")).join("\n"); + expect(output).toContain("trace-2"); + expect(output).toContain("trace-3"); + logSpy.mockRestore(); + }); +}); diff --git a/packages/cli/src/commands/prune.test.ts b/packages/cli/src/commands/prune.test.ts new file mode 100644 index 0000000..e2766bf --- /dev/null +++ b/packages/cli/src/commands/prune.test.ts @@ -0,0 +1,111 @@ +import { describe, it, expect, afterEach, vi } from "vitest"; +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { Command } from "commander"; +import { SqliteStore } from "@obyflow/core"; +import { registerPruneCommand } from "./prune.js"; + +function buildProgram(): Command { + const program = new Command(); + program.exitOverride(); + registerPruneCommand(program); + return program; +} + +function seedEvents(dbPath: string, timestamps: string[]): void { + const store = new SqliteStore(dbPath); + timestamps.forEach((timestamp, i) => { + store.insert({ + id: `evt-${i}`, + type: "log", + trace_id: `trace-${i}`, + request_id: null, + service: "checkout-service", + host: null, + container: null, + deployment_id: null, + timestamp, + duration_ms: null, + attributes: {}, + severity: "info", + }); + }); + store.close(); +} + +describe("obyflow prune", () => { + let dir: string; + + afterEach(() => { + if (dir) rmSync(dir, { recursive: true, force: true }); + }); + + it("rejects an invalid --older-than value", async () => { + dir = mkdtempSync(join(tmpdir(), "obyflow-cli-prune-")); + const dbPath = join(dir, "test.db"); + seedEvents(dbPath, [new Date().toISOString()]); + + const logSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + const program = buildProgram(); + + await program.parseAsync(["prune", "--db", dbPath, "--older-than", "nonsense"], { + from: "user", + }); + + const output = logSpy.mock.calls.map((call) => call.join(" ")).join("\n"); + expect(output).toContain('Invalid --older-than value "nonsense"'); + logSpy.mockRestore(); + + const store = new SqliteStore(dbPath); + expect(store.countAll()).toBe(1); + store.close(); + }); + + it("does not delete anything without --yes", async () => { + dir = mkdtempSync(join(tmpdir(), "obyflow-cli-prune-")); + const dbPath = join(dir, "test.db"); + const old = new Date(Date.now() - 40 * 86_400_000).toISOString(); + seedEvents(dbPath, [old]); + + const logSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + const program = buildProgram(); + + await program.parseAsync(["prune", "--db", dbPath, "--older-than", "30d"], { + from: "user", + }); + + const output = logSpy.mock.calls.map((call) => call.join(" ")).join("\n"); + expect(output).toContain("This will permanently delete events older than"); + expect(output).toContain("Re-run with --yes to confirm."); + logSpy.mockRestore(); + + const store = new SqliteStore(dbPath); + expect(store.countAll()).toBe(1); + store.close(); + }); + + it("deletes only events older than the threshold when --yes is passed", async () => { + dir = mkdtempSync(join(tmpdir(), "obyflow-cli-prune-")); + const dbPath = join(dir, "test.db"); + const old = new Date(Date.now() - 40 * 86_400_000).toISOString(); + const recent = new Date().toISOString(); + seedEvents(dbPath, [old, recent]); + + const logSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + const program = buildProgram(); + + await program.parseAsync( + ["prune", "--db", dbPath, "--older-than", "30d", "--yes"], + { from: "user" }, + ); + + const output = logSpy.mock.calls.map((call) => call.join(" ")).join("\n"); + expect(output).toContain("Deleted 1 event(s) older than"); + logSpy.mockRestore(); + + const store = new SqliteStore(dbPath); + expect(store.countAll()).toBe(1); + store.close(); + }); +});