Skip to content

Commit 537571d

Browse files
jason-rlclaude
andcommitted
test: add 131 tests covering PR #230 additions
Cover new CLI commands (axon events, blueprint duplicate, scenario list), new/modified services (axonService, benchmarkJobService, benchmarkService, objectService), and the allocateSectionLines pure function. Export allocateSectionLines for direct unit testing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7c59b15 commit 537571d

11 files changed

Lines changed: 2084 additions & 1 deletion

File tree

src/components/ResourceDetailPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export interface SectionAllocation {
5757
/**
5858
* Allocate available lines to sections. Section order = priority (first sections get space first).
5959
*/
60-
function allocateSectionLines(
60+
export function allocateSectionLines(
6161
detailSections: DetailSection[],
6262
linesAvailable: number,
6363
): SectionAllocation {
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
import { jest, describe, it, expect, beforeEach } from "@jest/globals";
2+
3+
const mockListAxonEvents = jest.fn();
4+
jest.unstable_mockModule("@/services/axonService.js", () => ({
5+
listAxonEvents: mockListAxonEvents,
6+
}));
7+
8+
const mockOutput = jest.fn();
9+
const mockOutputError = jest.fn();
10+
const mockParseLimit = jest.fn();
11+
jest.unstable_mockModule("@/utils/output.js", () => ({
12+
output: mockOutput,
13+
outputError: mockOutputError,
14+
parseLimit: mockParseLimit,
15+
}));
16+
17+
const { listAxonEventsCommand } = await import("@/commands/axon/events.js");
18+
19+
describe("listAxonEventsCommand", () => {
20+
beforeEach(() => {
21+
jest.clearAllMocks();
22+
(console.log as jest.Mock).mockClear();
23+
mockParseLimit.mockReturnValue(Infinity);
24+
});
25+
26+
it("prints table with events", async () => {
27+
const events = [
28+
{
29+
sequence: 1,
30+
timestamp_ms: 1700000000000,
31+
origin: "api",
32+
source: "user",
33+
event_type: "create",
34+
payload: '{"key":"value"}',
35+
},
36+
{
37+
sequence: 2,
38+
timestamp_ms: 1700000001000,
39+
origin: "system",
40+
source: "agent",
41+
event_type: "update",
42+
payload: '{"data":"test"}',
43+
},
44+
];
45+
mockListAxonEvents.mockResolvedValue({ events, hasMore: false });
46+
47+
await listAxonEventsCommand("axn_1", {});
48+
49+
const calls = (console.log as jest.Mock).mock.calls;
50+
const allOutput = calls.map((c: any[]) => String(c[0])).join("\n");
51+
expect(allOutput).toContain("2 events");
52+
});
53+
54+
it("defaults to limit 50 when parseLimit returns Infinity", async () => {
55+
mockParseLimit.mockReturnValue(Infinity);
56+
mockListAxonEvents.mockResolvedValue({ events: [], hasMore: false });
57+
58+
await listAxonEventsCommand("axn_1", {});
59+
60+
expect(mockListAxonEvents).toHaveBeenCalledWith("axn_1", { limit: 50 });
61+
});
62+
63+
it("uses explicit limit", async () => {
64+
mockParseLimit.mockReturnValue(10);
65+
mockListAxonEvents.mockResolvedValue({ events: [], hasMore: false });
66+
67+
await listAxonEventsCommand("axn_1", { limit: "10" });
68+
69+
expect(mockListAxonEvents).toHaveBeenCalledWith("axn_1", { limit: 10 });
70+
});
71+
72+
it("prints 'No events found' for empty results", async () => {
73+
mockListAxonEvents.mockResolvedValue({ events: [], hasMore: false });
74+
75+
await listAxonEventsCommand("axn_1", {});
76+
77+
const calls = (console.log as jest.Mock).mock.calls;
78+
const allOutput = calls.map((c: any[]) => String(c[0])).join("\n");
79+
expect(allOutput).toContain("No events found");
80+
});
81+
82+
it("shows pagination hint when hasMore is true", async () => {
83+
const events = [
84+
{
85+
sequence: 1,
86+
timestamp_ms: 1700000000000,
87+
origin: "api",
88+
source: "user",
89+
event_type: "create",
90+
payload: "{}",
91+
},
92+
];
93+
mockListAxonEvents.mockResolvedValue({ events, hasMore: true });
94+
95+
await listAxonEventsCommand("axn_1", {});
96+
97+
const calls = (console.log as jest.Mock).mock.calls;
98+
const allOutput = calls.map((c: any[]) => String(c[0])).join("\n");
99+
expect(allOutput).toContain("More events available");
100+
});
101+
102+
it("does not show pagination hint when hasMore is false", async () => {
103+
const events = [
104+
{
105+
sequence: 1,
106+
timestamp_ms: 1700000000000,
107+
origin: "api",
108+
source: "user",
109+
event_type: "create",
110+
payload: "{}",
111+
},
112+
];
113+
mockListAxonEvents.mockResolvedValue({ events, hasMore: false });
114+
115+
await listAxonEventsCommand("axn_1", {});
116+
117+
const calls = (console.log as jest.Mock).mock.calls;
118+
const allOutput = calls.map((c: any[]) => String(c[0])).join("\n");
119+
expect(allOutput).not.toContain("More events available");
120+
});
121+
122+
it("uses output() for JSON format", async () => {
123+
const events = [{ sequence: 1, timestamp_ms: 1700000000000, origin: "api", source: "user", event_type: "create", payload: "{}" }];
124+
mockListAxonEvents.mockResolvedValue({ events, hasMore: false });
125+
126+
await listAxonEventsCommand("axn_1", { output: "json" });
127+
128+
expect(mockOutput).toHaveBeenCalledWith(events, {
129+
format: "json",
130+
defaultFormat: "json",
131+
});
132+
// Should not print table
133+
expect(console.log).not.toHaveBeenCalled();
134+
});
135+
136+
it("uses output() for YAML format", async () => {
137+
const events = [{ sequence: 1, timestamp_ms: 1700000000000, origin: "api", source: "user", event_type: "create", payload: "{}" }];
138+
mockListAxonEvents.mockResolvedValue({ events, hasMore: false });
139+
140+
await listAxonEventsCommand("axn_1", { output: "yaml" });
141+
142+
expect(mockOutput).toHaveBeenCalledWith(events, {
143+
format: "yaml",
144+
defaultFormat: "json",
145+
});
146+
});
147+
148+
it("does not use output() for text format", async () => {
149+
mockListAxonEvents.mockResolvedValue({ events: [], hasMore: false });
150+
151+
await listAxonEventsCommand("axn_1", { output: "text" });
152+
153+
expect(mockOutput).not.toHaveBeenCalled();
154+
});
155+
156+
it("truncates payload to 60 chars", async () => {
157+
const longPayload = "A".repeat(80);
158+
const events = [
159+
{
160+
sequence: 1,
161+
timestamp_ms: 1700000000000,
162+
origin: "api",
163+
source: "user",
164+
event_type: "create",
165+
payload: longPayload,
166+
},
167+
];
168+
mockListAxonEvents.mockResolvedValue({ events, hasMore: false });
169+
170+
await listAxonEventsCommand("axn_1", {});
171+
172+
const calls = (console.log as jest.Mock).mock.calls;
173+
// Data row is the 3rd call (after header and separator)
174+
const dataRow = String(calls[2][0]);
175+
expect(dataRow).not.toContain(longPayload);
176+
expect(dataRow).toContain("…");
177+
});
178+
179+
it("truncates long origin with ellipsis", async () => {
180+
const events = [
181+
{
182+
sequence: 1,
183+
timestamp_ms: 1700000000000,
184+
origin: "a-very-long-origin-name",
185+
source: "user",
186+
event_type: "create",
187+
payload: "{}",
188+
},
189+
];
190+
mockListAxonEvents.mockResolvedValue({ events, hasMore: false });
191+
192+
await listAxonEventsCommand("axn_1", {});
193+
194+
const dataRow = String((console.log as jest.Mock).mock.calls[2][0]);
195+
expect(dataRow).toContain("…");
196+
});
197+
198+
it("shows singular 'event' for single result", async () => {
199+
const events = [
200+
{
201+
sequence: 1,
202+
timestamp_ms: 1700000000000,
203+
origin: "api",
204+
source: "user",
205+
event_type: "create",
206+
payload: "{}",
207+
},
208+
];
209+
mockListAxonEvents.mockResolvedValue({ events, hasMore: false });
210+
211+
await listAxonEventsCommand("axn_1", {});
212+
213+
const calls = (console.log as jest.Mock).mock.calls;
214+
const allOutput = calls.map((c: any[]) => String(c[0])).join("\n");
215+
expect(allOutput).toContain("1 event");
216+
expect(allOutput).not.toContain("1 events");
217+
});
218+
219+
it("handles API error gracefully", async () => {
220+
const error = new Error("Network error");
221+
mockListAxonEvents.mockRejectedValue(error);
222+
223+
await listAxonEventsCommand("axn_1", {});
224+
225+
expect(mockOutputError).toHaveBeenCalledWith(
226+
"Failed to get axon events",
227+
error,
228+
);
229+
});
230+
});

0 commit comments

Comments
 (0)