From cdfde7e0b9241eac0623a67cd3805c7fbcd43cb8 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 12 Apr 2026 03:29:17 +0100 Subject: [PATCH] test(commands): share status test fixtures --- src/commands/status-overview-rows.test.ts | 120 +--------- src/commands/status-overview-surface.test.ts | 75 ++---- .../status.command-report-data.test.ts | 131 ++--------- src/commands/status.test-support.ts | 222 ++++++++++++++++++ 4 files changed, 270 insertions(+), 278 deletions(-) create mode 100644 src/commands/status.test-support.ts diff --git a/src/commands/status-overview-rows.test.ts b/src/commands/status-overview-rows.test.ts index b4476b8d2c7..451e72d8543 100644 --- a/src/commands/status-overview-rows.test.ts +++ b/src/commands/status-overview-rows.test.ts @@ -3,94 +3,14 @@ import { buildStatusAllOverviewRows, buildStatusCommandOverviewRows, } from "./status-overview-rows.ts"; +import { + baseStatusOverviewSurface, + createStatusCommandOverviewRowsParams, +} from "./status.test-support.ts"; describe("status-overview-rows", () => { it("builds command overview rows from the shared surface", () => { - expect( - buildStatusCommandOverviewRows({ - opts: { deep: true }, - surface: { - cfg: { update: { channel: "stable" }, gateway: { bind: "loopback" } }, - update: { - installKind: "git", - git: { - branch: "main", - tag: "v1.2.3", - upstream: "origin/main", - behind: 2, - ahead: 0, - dirty: false, - fetchOk: true, - }, - registry: { latestVersion: "2026.4.10" }, - } as never, - tailscaleMode: "serve", - tailscaleDns: "box.tail.ts.net", - tailscaleHttpsUrl: "https://box.tail.ts.net", - gatewayMode: "remote", - remoteUrlMissing: false, - gatewayConnection: { url: "wss://gateway.example.com", urlSource: "config" }, - gatewayReachable: true, - gatewayProbe: { connectLatencyMs: 42, error: null }, - gatewayProbeAuth: { token: "tok" }, - gatewayProbeAuthWarning: "warn-text", - gatewaySelf: { host: "gateway", version: "1.2.3" }, - gatewayService: { - label: "LaunchAgent", - installed: true, - managedByOpenClaw: true, - loadedText: "loaded", - runtimeShort: "running", - }, - nodeService: { - label: "node", - installed: true, - loadedText: "loaded", - runtime: { status: "running", pid: 42 }, - }, - nodeOnlyGateway: null, - }, - osLabel: "macOS", - summary: { - tasks: { total: 3, active: 1, failures: 0, byStatus: { queued: 1, running: 1 } }, - taskAudit: { errors: 1, warnings: 0 }, - heartbeat: { - agents: [{ agentId: "main", enabled: true, everyMs: 60_000, every: "1m" }], - }, - queuedSystemEvents: ["one", "two"], - sessions: { - count: 2, - paths: ["store.json"], - defaults: { model: "gpt-5.4", contextTokens: 12_000 }, - }, - }, - health: { durationMs: 42 }, - lastHeartbeat: { - ts: Date.now() - 30_000, - status: "ok", - channel: "discord", - accountId: "acct", - }, - agentStatus: { - defaultId: "main", - bootstrapPendingCount: 1, - totalSessions: 2, - agents: [{ id: "main", lastActiveAgeMs: 60_000 }], - }, - memory: { files: 1, chunks: 2, vector: {}, fts: {}, cache: {} }, - memoryPlugin: { enabled: true, slot: "memory" }, - pluginCompatibility: [{ pluginId: "a", severity: "warn", message: "legacy" }], - ok: (value: string) => `ok(${value})`, - warn: (value: string) => `warn(${value})`, - muted: (value: string) => `muted(${value})`, - formatTimeAgo: (value: number) => `${value}ms`, - formatKTokens: (value: number) => `${Math.round(value / 1000)}k`, - resolveMemoryVectorState: () => ({ state: "ready", tone: "ok" }), - resolveMemoryFtsState: () => ({ state: "ready", tone: "warn" }), - resolveMemoryCacheSummary: () => ({ text: "cache warm", tone: "muted" }), - updateValue: "available · custom update", - } as unknown as Parameters[0]), - ).toEqual( + expect(buildStatusCommandOverviewRows(createStatusCommandOverviewRowsParams())).toEqual( expect.arrayContaining([ { Item: "OS", Value: `macOS · node ${process.versions.node}` }, { @@ -108,36 +28,10 @@ describe("status-overview-rows", () => { expect( buildStatusAllOverviewRows({ surface: { - cfg: { update: { channel: "stable" }, gateway: { bind: "loopback" } }, - update: { - installKind: "git", - git: { branch: "main", tag: "v1.2.3", upstream: "origin/main" }, - } as never, + ...baseStatusOverviewSurface, tailscaleMode: "off", - tailscaleDns: "box.tail.ts.net", tailscaleHttpsUrl: null, - gatewayMode: "remote", - remoteUrlMissing: false, gatewayConnection: { url: "wss://gateway.example.com", urlSource: "config" }, - gatewayReachable: true, - gatewayProbe: { connectLatencyMs: 42, error: null }, - gatewayProbeAuth: { token: "tok" }, - gatewayProbeAuthWarning: "warn-text", - gatewaySelf: { host: "gateway", version: "1.2.3" }, - gatewayService: { - label: "LaunchAgent", - installed: true, - managedByOpenClaw: true, - loadedText: "loaded", - runtimeShort: "running", - }, - nodeService: { - label: "node", - installed: true, - loadedText: "loaded", - runtime: { status: "running", pid: 42 }, - }, - nodeOnlyGateway: null, }, osLabel: "macOS", configPath: "/tmp/openclaw.json", @@ -148,7 +42,7 @@ describe("status-overview-rows", () => { agents: [{ id: "main", lastActiveAgeMs: 60_000 }], }, tailscaleBackendState: "Running", - } as unknown as Parameters[0]), + }), ).toEqual( expect.arrayContaining([ { Item: "Version", Value: expect.any(String) }, diff --git a/src/commands/status-overview-surface.test.ts b/src/commands/status-overview-surface.test.ts index 9728f4a0d31..9d91ce4efb8 100644 --- a/src/commands/status-overview-surface.test.ts +++ b/src/commands/status-overview-surface.test.ts @@ -5,86 +5,47 @@ import { buildStatusOverviewSurfaceFromOverview, buildStatusOverviewSurfaceFromScan, } from "./status-overview-surface.ts"; - -const baseCfg = { update: { channel: "stable" }, gateway: { bind: "loopback" } } as const; -const baseUpdate = { installKind: "git", git: { branch: "main", tag: "v1.2.3" } } as never; -const baseGatewaySnapshot = { - gatewayMode: "remote", - remoteUrlMissing: false, - gatewayConnection: { - url: "wss://gateway.example.com", - urlSource: "config", - message: "Gateway target: wss://gateway.example.com", - }, - gatewayReachable: true, - gatewayProbe: { connectLatencyMs: 42, error: null } as never, - gatewayProbeAuth: { token: "tok" }, - gatewayProbeAuthWarning: "warn-text", - gatewaySelf: { host: "gateway", version: "1.2.3" }, -} as const; -const baseScanFields = { - cfg: baseCfg, - update: baseUpdate, - tailscaleMode: "serve", - tailscaleDns: "box.tail.ts.net", - tailscaleHttpsUrl: "https://box.tail.ts.net", - ...baseGatewaySnapshot, -}; -const baseGatewayService = { - label: "LaunchAgent", - installed: true, - managedByOpenClaw: true, - loadedText: "loaded", - runtimeShort: "running", -}; -const baseNodeService = { - label: "node", - installed: true, - loadedText: "loaded", - runtime: { status: "running", pid: 42 }, -}; -const baseServices = { - gatewayService: baseGatewayService, - nodeService: baseNodeService, - nodeOnlyGateway: null, -}; -const baseOverviewSurface = { - ...baseScanFields, - ...baseServices, -}; +import { + baseStatusCfg, + baseStatusGatewaySnapshot, + baseStatusOverviewScanFields, + baseStatusOverviewSurface, + baseStatusServices, + baseStatusUpdate, +} from "./status.test-support.ts"; describe("status-overview-surface", () => { it("builds the shared overview surface from a status scan result", () => { expect( buildStatusOverviewSurfaceFromScan({ - scan: baseScanFields, - ...baseServices, + scan: baseStatusOverviewScanFields, + ...baseStatusServices, }), - ).toEqual(baseOverviewSurface); + ).toEqual(baseStatusOverviewSurface); }); it("builds the shared overview surface from scan overview data", () => { expect( buildStatusOverviewSurfaceFromOverview({ overview: { - cfg: baseCfg, - update: baseUpdate, + cfg: baseStatusCfg, + update: baseStatusUpdate, tailscaleMode: "serve", tailscaleDns: "box.tail.ts.net", tailscaleHttpsUrl: "https://box.tail.ts.net", - gatewaySnapshot: baseGatewaySnapshot, + gatewaySnapshot: baseStatusGatewaySnapshot, } as never, - ...baseServices, + ...baseStatusServices, }), - ).toEqual(baseOverviewSurface); + ).toEqual(baseStatusOverviewSurface); }); it("builds overview rows from the shared surface bundle", () => { expect( buildStatusOverviewRowsFromSurface({ surface: { - ...baseOverviewSurface, - cfg: baseCfg, + ...baseStatusOverviewSurface, + cfg: baseStatusCfg, update: { installKind: "git", git: { diff --git a/src/commands/status.command-report-data.test.ts b/src/commands/status.command-report-data.test.ts index 016a3f1a3a5..43545bbd0ed 100644 --- a/src/commands/status.command-report-data.test.ts +++ b/src/commands/status.command-report-data.test.ts @@ -1,118 +1,33 @@ import { describe, expect, it } from "vitest"; import { buildStatusCommandReportData } from "./status.command-report-data.ts"; +import { createStatusCommandReportDataParams } from "./status.test-support.ts"; describe("buildStatusCommandReportData", () => { it("builds report inputs from shared status surfaces", async () => { - const result = await buildStatusCommandReportData({ - opts: { deep: true, verbose: true }, - surface: { - cfg: { update: { channel: "stable" }, gateway: { bind: "loopback" } }, - update: { - installKind: "git", - git: { - branch: "main", - tag: "v1.2.3", - upstream: "origin/main", - behind: 2, - ahead: 0, - dirty: false, - fetchOk: true, + const baseParams = createStatusCommandReportDataParams(); + const result = await buildStatusCommandReportData( + createStatusCommandReportDataParams({ + surface: { + ...baseParams.surface, + gatewayProbe: { connectLatencyMs: 123, error: null }, + }, + summary: { + ...baseParams.summary, + sessions: { + ...baseParams.summary.sessions, + recent: [ + { + key: "session-key", + kind: "direct", + updatedAt: 1, + age: 5_000, + model: "gpt-5.4", + }, + ], }, - registry: { latestVersion: "2026.4.10" }, - } as never, - tailscaleMode: "serve", - tailscaleDns: "box.tail.ts.net", - tailscaleHttpsUrl: "https://box.tail.ts.net", - gatewayMode: "remote", - remoteUrlMissing: false, - gatewayConnection: { url: "wss://gateway.example.com", urlSource: "config" }, - gatewayReachable: true, - gatewayProbe: { connectLatencyMs: 123, error: null }, - gatewayProbeAuth: { token: "tok" }, - gatewayProbeAuthWarning: "warn-text", - gatewaySelf: { host: "gateway", version: "1.2.3" }, - gatewayService: { - label: "LaunchAgent", - installed: true, - managedByOpenClaw: true, - loadedText: "loaded", - runtimeShort: "running", }, - nodeService: { - label: "node", - installed: true, - loadedText: "loaded", - runtime: { status: "running", pid: 42 }, - }, - nodeOnlyGateway: null, - }, - osSummary: { label: "macOS" }, - summary: { - tasks: { total: 3, active: 1, failures: 0, byStatus: { queued: 1, running: 1 } }, - taskAudit: { errors: 1, warnings: 0 }, - heartbeat: { agents: [{ agentId: "main", enabled: true, everyMs: 60_000, every: "1m" }] }, - queuedSystemEvents: ["one", "two"], - sessions: { - count: 2, - paths: ["store.json"], - defaults: { model: "gpt-5.4", contextTokens: 12_000 }, - recent: [ - { key: "session-key", kind: "chat", updatedAt: 1, age: 5_000, model: "gpt-5.4" }, - ], - }, - }, - securityAudit: { - summary: { critical: 0, warn: 1, info: 0 }, - findings: [{ severity: "warn", title: "Warn first", detail: "warn detail" }], - }, - health: { durationMs: 42 }, - usageLines: ["usage line"], - lastHeartbeat: { - ts: Date.now() - 30_000, - status: "ok", - channel: "discord", - accountId: "acct", - }, - agentStatus: { - defaultId: "main", - bootstrapPendingCount: 1, - totalSessions: 2, - agents: [{ id: "main", lastActiveAgeMs: 60_000 }], - }, - channels: { - rows: [{ id: "discord", label: "Discord", enabled: true, state: "ok", detail: "ready" }], - }, - channelIssues: [{ channel: "discord", message: "warn msg" }], - memory: { files: 1, chunks: 2, vector: {}, fts: {}, cache: {} }, - memoryPlugin: { enabled: true, slot: "memory" }, - pluginCompatibility: [{ pluginId: "a", severity: "warn", message: "legacy" }], - pairingRecovery: { requestId: "req-1" }, - tableWidth: 120, - ok: (value: string) => `ok(${value})`, - warn: (value: string) => `warn(${value})`, - muted: (value: string) => `muted(${value})`, - shortenText: (value: string) => value, - formatCliCommand: (value: string) => `cmd:${value}`, - formatTimeAgo: (value: number) => `${value}ms`, - formatKTokens: (value: number) => `${Math.round(value / 1000)}k`, - formatTokensCompact: () => "12k", - formatPromptCacheCompact: () => "cache ok", - formatHealthChannelLines: () => ["Discord: OK · ready"], - formatPluginCompatibilityNotice: (notice: { message?: unknown }) => String(notice.message), - formatUpdateAvailableHint: () => "update available", - resolveMemoryVectorState: () => ({ state: "ready", tone: "ok" }), - resolveMemoryFtsState: () => ({ state: "ready", tone: "warn" }), - resolveMemoryCacheSummary: () => ({ text: "cache warm", tone: "muted" }), - accentDim: (value: string) => `accent(${value})`, - theme: { - heading: (value: string) => `# ${value}`, - muted: (value: string) => `muted(${value})`, - warn: (value: string) => `warn(${value})`, - error: (value: string) => `error(${value})`, - }, - renderTable: ({ rows }: { rows: Array> }) => `table:${rows.length}`, - updateValue: "available · custom update", - } as unknown as Parameters[0]); + }), + ); expect(result.overviewRows[0]).toEqual({ Item: "OS", diff --git a/src/commands/status.test-support.ts b/src/commands/status.test-support.ts new file mode 100644 index 00000000000..9d629fe2a77 --- /dev/null +++ b/src/commands/status.test-support.ts @@ -0,0 +1,222 @@ +import type { HeartbeatEventPayload } from "../infra/heartbeat-events.js"; +import type { Tone } from "../memory-host-sdk/status.js"; +import type { PluginCompatibilityNotice } from "../plugins/status.js"; +import type { buildStatusCommandOverviewRows } from "./status-overview-rows.ts"; +import type { StatusOverviewSurface } from "./status-overview-surface.ts"; +import type { AgentLocalStatus } from "./status.agent-local.js"; +import type { buildStatusCommandReportData } from "./status.command-report-data.ts"; +import type { MemoryPluginStatus, MemoryStatusSnapshot } from "./status.scan.shared.js"; +import type { StatusSummary } from "./status.types.js"; + +type StatusCommandOverviewRowsParams = Parameters[0]; +type StatusCommandReportDataParams = Parameters[0]; + +export const baseStatusCfg = { + update: { channel: "stable" }, + gateway: { bind: "loopback" }, +} as const; + +export const baseStatusUpdate = { + installKind: "git", + git: { + branch: "main", + tag: "v1.2.3", + upstream: "origin/main", + behind: 2, + ahead: 0, + dirty: false, + fetchOk: true, + }, + registry: { latestVersion: "2026.4.10" }, +} as never; + +export const baseStatusGatewaySnapshot = { + gatewayMode: "remote", + remoteUrlMissing: false, + gatewayConnection: { + url: "wss://gateway.example.com", + urlSource: "config", + message: "Gateway target: wss://gateway.example.com", + }, + gatewayReachable: true, + gatewayProbe: { connectLatencyMs: 42, error: null } as never, + gatewayProbeAuth: { token: "tok" }, + gatewayProbeAuthWarning: "warn-text", + gatewaySelf: { host: "gateway", version: "1.2.3" }, +} as const; + +export const baseStatusOverviewScanFields = { + cfg: baseStatusCfg, + update: baseStatusUpdate, + tailscaleMode: "serve", + tailscaleDns: "box.tail.ts.net", + tailscaleHttpsUrl: "https://box.tail.ts.net", + ...baseStatusGatewaySnapshot, +}; + +export const baseStatusGatewayService = { + label: "LaunchAgent", + installed: true, + managedByOpenClaw: true, + loadedText: "loaded", + runtimeShort: "running", +}; + +export const baseStatusNodeService = { + label: "node", + installed: true, + loadedText: "loaded", + runtime: { status: "running", pid: 42 }, +}; + +export const baseStatusServices = { + gatewayService: baseStatusGatewayService, + nodeService: baseStatusNodeService, + nodeOnlyGateway: null, +}; + +export const baseStatusOverviewSurface = { + ...baseStatusOverviewScanFields, + ...baseStatusServices, +} as unknown as StatusOverviewSurface; + +export const baseStatusSummary = { + tasks: { total: 3, active: 1, failures: 0, byStatus: { queued: 1, running: 1 } }, + taskAudit: { errors: 1, warnings: 0 }, + heartbeat: { + defaultAgentId: "main", + agents: [{ agentId: "main", enabled: true, everyMs: 60_000, every: "1m" }], + }, + channelSummary: [], + queuedSystemEvents: ["one", "two"], + sessions: { + count: 2, + paths: ["store.json"], + defaults: { model: "gpt-5.4", contextTokens: 12_000 }, + recent: [{ key: "session-key", kind: "direct", updatedAt: 1, age: 5_000, model: "gpt-5.4" }], + byAgent: [], + }, +} as unknown as StatusSummary; + +export const baseStatusAgentStatus = { + defaultId: "main", + bootstrapPendingCount: 1, + totalSessions: 2, + agents: [{ id: "main", lastActiveAgeMs: 60_000 }] as AgentLocalStatus[], +}; + +export const baseStatusMemory = { + agentId: "main", + files: 1, + chunks: 2, + vector: {}, + fts: {}, + cache: {}, +} as unknown as MemoryStatusSnapshot; + +export const baseStatusMemoryPlugin = { + enabled: true, + slot: "memory", +} as const satisfies MemoryPluginStatus; + +export const baseStatusPluginCompatibility = [ + { pluginId: "a", severity: "warn", message: "legacy" }, +] as PluginCompatibilityNotice[]; + +export function createStatusLastHeartbeat(): HeartbeatEventPayload { + return { + ts: Date.now() - 30_000, + status: "ok", + channel: "discord", + accountId: "acct", + }; +} + +export const statusTestDecorators = { + ok: (value: string) => `ok(${value})`, + warn: (value: string) => `warn(${value})`, + muted: (value: string) => `muted(${value})`, + accentDim: (value: string) => `accent(${value})`, +}; + +export const statusTestFormatting = { + shortenText: (value: string) => value, + formatCliCommand: (value: string) => `cmd:${value}`, + formatTimeAgo: (value: number) => `${value}ms`, + formatKTokens: (value: number) => `${Math.round(value / 1000)}k`, + formatTokensCompact: () => "12k", + formatPromptCacheCompact: () => "cache ok", + formatHealthChannelLines: () => ["Discord: OK · ready"], + formatPluginCompatibilityNotice: (notice: { message?: unknown }) => String(notice.message), + formatUpdateAvailableHint: () => "update available", +}; + +export const statusTestMemoryResolvers = { + resolveMemoryVectorState: () => ({ state: "ready", tone: "ok" as Tone }), + resolveMemoryFtsState: () => ({ state: "ready", tone: "warn" as Tone }), + resolveMemoryCacheSummary: () => ({ text: "cache warm", tone: "muted" as Tone }), +}; + +export const statusTestTheme = { + heading: (value: string) => `# ${value}`, + muted: (value: string) => `muted(${value})`, + warn: (value: string) => `warn(${value})`, + error: (value: string) => `error(${value})`, +}; + +export function createStatusCommandOverviewRowsParams( + overrides: Partial = {}, +): StatusCommandOverviewRowsParams { + return { + opts: { deep: true }, + surface: baseStatusOverviewSurface, + osLabel: "macOS", + summary: baseStatusSummary, + health: { durationMs: 42 }, + lastHeartbeat: createStatusLastHeartbeat(), + agentStatus: baseStatusAgentStatus, + memory: baseStatusMemory, + memoryPlugin: baseStatusMemoryPlugin, + pluginCompatibility: baseStatusPluginCompatibility, + ...statusTestDecorators, + ...statusTestFormatting, + ...statusTestMemoryResolvers, + updateValue: "available · custom update", + ...overrides, + }; +} + +export function createStatusCommandReportDataParams( + overrides: Partial = {}, +): StatusCommandReportDataParams { + return { + opts: { deep: true, verbose: true }, + surface: baseStatusOverviewSurface, + osSummary: { label: "macOS" } as never, + summary: baseStatusSummary, + securityAudit: { + summary: { critical: 0, warn: 1, info: 0 }, + findings: [{ severity: "warn", title: "Warn first", detail: "warn detail" }], + }, + health: { durationMs: 42 }, + usageLines: ["usage line"], + lastHeartbeat: createStatusLastHeartbeat(), + agentStatus: baseStatusAgentStatus, + channels: { + rows: [{ id: "discord", label: "Discord", enabled: true, state: "ok", detail: "ready" }], + }, + channelIssues: [{ channel: "discord", message: "warn msg" }], + memory: baseStatusMemory, + memoryPlugin: baseStatusMemoryPlugin, + pluginCompatibility: baseStatusPluginCompatibility, + pairingRecovery: { requestId: "req-1" }, + tableWidth: 120, + ...statusTestDecorators, + ...statusTestFormatting, + ...statusTestMemoryResolvers, + theme: statusTestTheme, + renderTable: ({ rows }: { rows: Array> }) => `table:${rows.length}`, + updateValue: "available · custom update", + ...overrides, + }; +}