Skip to content

Commit 8f92e08

Browse files
author
Sorra
committed
SB-0MN77JB3S0FSIAL3: Restore CLI-backed /stats command handler
1 parent 6be7c09 commit 8f92e08

6 files changed

Lines changed: 270 additions & 18 deletions

File tree

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,60 @@
11
import type { CommandInteraction } from "discord.js";
2+
import { runStatsCommand, type StatsResult } from "../bot/cli-runner.js";
23
import type { SlashCommandHandler } from "../interfaces/command-handler.js";
34

4-
const DEFAULT_UNAVAILABLE_MESSAGE = "Stats functionality temporarily unavailable - CLI has been extracted to openBrain repository.";
5+
const DEFAULT_ERROR_MESSAGE = "❌ Failed to retrieve OpenBrain statistics. Please try again.";
56

67
export interface StatsCommandHandlerDependencies {
7-
unavailableMessage?: string;
8+
runStats?: typeof runStatsCommand;
9+
errorMessage?: string;
810
}
911

1012
export class StatsCommandHandler implements SlashCommandHandler {
11-
private readonly unavailableMessage: string;
13+
private readonly runStats: typeof runStatsCommand;
14+
private readonly errorMessage: string;
1215

1316
constructor(dependencies: StatsCommandHandlerDependencies = {}) {
14-
this.unavailableMessage = dependencies.unavailableMessage ?? DEFAULT_UNAVAILABLE_MESSAGE;
17+
this.runStats = dependencies.runStats ?? runStatsCommand;
18+
this.errorMessage = dependencies.errorMessage ?? DEFAULT_ERROR_MESSAGE;
1519
}
1620

1721
async handleCommand(command: CommandInteraction): Promise<boolean> {
1822
if (command.commandName !== "stats") {
1923
return false;
2024
}
2125

22-
await command.reply(this.unavailableMessage);
26+
await command.deferReply();
27+
28+
try {
29+
const stats = await this.runStats({
30+
channelId: command.channelId ?? undefined,
31+
messageId: command.id,
32+
authorId: command.user?.id,
33+
});
34+
35+
await command.editReply(this.formatStatsMessage(stats));
36+
} catch {
37+
await command.editReply(this.errorMessage);
38+
}
39+
2340
return true;
2441
}
42+
43+
private formatStatsMessage(stats: StatsResult): string {
44+
const totalLinks = stats.totalLinks;
45+
const processedCount = stats.processedCount;
46+
const pendingCount = stats.pendingCount;
47+
const failedCount = stats.failedCount;
48+
const successRate = totalLinks > 0 ? ((processedCount / totalLinks) * 100).toFixed(1) : "0.0";
49+
50+
return [
51+
"📊 OpenBrain statistics",
52+
"",
53+
`Total links: ${totalLinks.toLocaleString()}`,
54+
`Processed: ${processedCount.toLocaleString()}`,
55+
`Pending: ${pendingCount.toLocaleString()}`,
56+
`Failed: ${failedCount.toLocaleString()}`,
57+
`Success rate: ${successRate}%`,
58+
].join("\n");
59+
}
2560
}

tests/discord/interaction.test.ts

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ describe("slash interaction handlers", () => {
6868
runAddCommand: vi.fn(),
6969
runQueueCommand: vi.fn(),
7070
runSummaryCommand: vi.fn(),
71+
runStatsCommand: vi.fn(async () => ({
72+
totalLinks: 0,
73+
processedCount: 0,
74+
pendingCount: 0,
75+
failedCount: 0,
76+
})),
7177
isCliAvailable: vi.fn(async () => true),
7278
CliRunnerError: class MockCliRunnerError extends Error {},
7379
};
@@ -96,36 +102,59 @@ describe("slash interaction handlers", () => {
96102
});
97103

98104
it("routes /stats through StatsCommandHandler", async () => {
105+
const runStatsCommandMock = vi.fn(async () => ({
106+
totalLinks: 100,
107+
processedCount: 80,
108+
pendingCount: 15,
109+
failedCount: 5,
110+
}));
111+
99112
const handler = await loadInteractionHandler(async () => {
100113
await vi.doMock("../../src/bot/cli-runner.js", () => {
101114
return {
102115
runCliCommand: vi.fn(async () => ({ exitCode: 0, stdout: [] })),
103116
runAddCommand: vi.fn(),
104117
runQueueCommand: vi.fn(),
105118
runSummaryCommand: vi.fn(),
119+
runStatsCommand: runStatsCommandMock,
106120
isCliAvailable: vi.fn(async () => true),
107121
CliRunnerError: class MockCliRunnerError extends Error {},
108122
};
109123
});
110124
});
111125

112-
const replies: string[] = [];
126+
const edits: string[] = [];
113127
const fakeInteraction: any = {
114128
isCommand: () => true,
115129
commandName: "stats",
116130
options: { getString: vi.fn(), getInteger: vi.fn() },
117131
user: { id: "user-1" },
118132
channelId: "chan-1",
119133
deferReply: vi.fn(async () => {}),
120-
editReply: vi.fn(async (_content: string) => {}),
134+
id: "interaction-1",
135+
editReply: vi.fn(async (content: string) => edits.push(content)),
121136
fetchReply: vi.fn(async () => ({ id: "posted-1" })),
122-
reply: vi.fn(async (content: string) => replies.push(content)),
137+
reply: vi.fn(async (_content: string) => {}),
123138
};
124139

125140
await handler(fakeInteraction);
126141

127-
expect(replies).toContain(
128-
"Stats functionality temporarily unavailable - CLI has been extracted to openBrain repository."
142+
expect(fakeInteraction.deferReply).toHaveBeenCalledTimes(1);
143+
expect(runStatsCommandMock).toHaveBeenCalledWith({
144+
channelId: "chan-1",
145+
messageId: "interaction-1",
146+
authorId: "user-1",
147+
});
148+
expect(edits).toContain(
149+
[
150+
"📊 OpenBrain statistics",
151+
"",
152+
"Total links: 100",
153+
"Processed: 80",
154+
"Pending: 15",
155+
"Failed: 5",
156+
"Success rate: 80.0%",
157+
].join("\n")
129158
);
130159
});
131160

@@ -148,6 +177,12 @@ describe("slash interaction handlers", () => {
148177
runAddCommand: vi.fn(),
149178
runQueueCommand: vi.fn(),
150179
runSummaryCommand: vi.fn(),
180+
runStatsCommand: vi.fn(async () => ({
181+
totalLinks: 0,
182+
processedCount: 0,
183+
pendingCount: 0,
184+
failedCount: 0,
185+
})),
151186
isCliAvailable: vi.fn(async () => true),
152187
CliRunnerError: class MockCliRunnerError extends Error {},
153188
};
@@ -199,6 +234,12 @@ describe("slash interaction handlers", () => {
199234
runAddCommand: vi.fn(),
200235
runQueueCommand: vi.fn(),
201236
runSummaryCommand: vi.fn(),
237+
runStatsCommand: vi.fn(async () => ({
238+
totalLinks: 0,
239+
processedCount: 0,
240+
pendingCount: 0,
241+
failedCount: 0,
242+
})),
202243
isCliAvailable: vi.fn(async () => true),
203244
CliRunnerError: class MockCliRunnerError extends Error {},
204245
};
@@ -252,6 +293,12 @@ describe("slash interaction handlers", () => {
252293
runAddCommand: vi.fn(),
253294
runQueueCommand: vi.fn(),
254295
runSummaryCommand: vi.fn(),
296+
runStatsCommand: vi.fn(async () => ({
297+
totalLinks: 0,
298+
processedCount: 0,
299+
pendingCount: 0,
300+
failedCount: 0,
301+
})),
255302
isCliAvailable: vi.fn(async () => true),
256303
CliRunnerError: class MockCliRunnerError extends Error {},
257304
};
@@ -292,6 +339,12 @@ describe("slash interaction handlers", () => {
292339
runAddCommand: vi.fn(),
293340
runQueueCommand: vi.fn(),
294341
runSummaryCommand: vi.fn(),
342+
runStatsCommand: vi.fn(async () => ({
343+
totalLinks: 0,
344+
processedCount: 0,
345+
pendingCount: 0,
346+
failedCount: 0,
347+
})),
295348
isCliAvailable: vi.fn(async () => true),
296349
CliRunnerError: class MockCliRunnerError extends Error {},
297350
};
@@ -340,6 +393,12 @@ describe("slash interaction handlers", () => {
340393
runAddCommand: vi.fn(),
341394
runQueueCommand: vi.fn(),
342395
runSummaryCommand: vi.fn(),
396+
runStatsCommand: vi.fn(async () => ({
397+
totalLinks: 0,
398+
processedCount: 0,
399+
pendingCount: 0,
400+
failedCount: 0,
401+
})),
343402
isCliAvailable: vi.fn(async () => true),
344403
CliRunnerError: class MockCliRunnerError extends Error {},
345404
};

tests/index.ob-add.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ describe("ob add message handler failure modes", () => {
8484
runAddCommand: vi.fn(),
8585
runQueueCommand: vi.fn(),
8686
runSummaryCommand: vi.fn(),
87+
runStatsCommand: vi.fn(async () => ({
88+
totalLinks: 0,
89+
processedCount: 0,
90+
pendingCount: 0,
91+
failedCount: 0,
92+
})),
8793
isCliAvailable: vi.fn(async () => true),
8894
CliRunnerError: MockCliRunnerError,
8995
};
@@ -110,6 +116,12 @@ describe("ob add message handler failure modes", () => {
110116
runAddCommand: vi.fn(),
111117
runQueueCommand: vi.fn(),
112118
runSummaryCommand: vi.fn(),
119+
runStatsCommand: vi.fn(async () => ({
120+
totalLinks: 0,
121+
processedCount: 0,
122+
pendingCount: 0,
123+
failedCount: 0,
124+
})),
113125
isCliAvailable: vi.fn(async () => true),
114126
CliRunnerError: MockCliRunnerError,
115127
};
@@ -134,6 +146,12 @@ describe("ob add message handler failure modes", () => {
134146
runAddCommand: vi.fn(),
135147
runQueueCommand: vi.fn(),
136148
runSummaryCommand: vi.fn(),
149+
runStatsCommand: vi.fn(async () => ({
150+
totalLinks: 0,
151+
processedCount: 0,
152+
pendingCount: 0,
153+
failedCount: 0,
154+
})),
137155
isCliAvailable: vi.fn(async () => true),
138156
CliRunnerError: MockCliRunnerError,
139157
};

tests/index.test.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,12 @@ describe("index message handler integration", () => {
126126
url,
127127
summary: "Mock summary",
128128
})),
129+
runStatsCommand: vi.fn(async () => ({
130+
totalLinks: 0,
131+
processedCount: 0,
132+
pendingCount: 0,
133+
failedCount: 0,
134+
})),
129135
isCliAvailable: vi.fn(async () => true),
130136
CliRunnerError: MockCliRunnerError,
131137
};
@@ -170,6 +176,12 @@ describe("index message handler integration", () => {
170176
url,
171177
summary: "Mock summary",
172178
})),
179+
runStatsCommand: vi.fn(async () => ({
180+
totalLinks: 0,
181+
processedCount: 0,
182+
pendingCount: 0,
183+
failedCount: 0,
184+
})),
173185
isCliAvailable: vi.fn(async () => true),
174186
CliRunnerError: MockCliRunnerError,
175187
};
@@ -218,6 +230,12 @@ describe("index message handler integration", () => {
218230
url,
219231
summary: "Mock summary",
220232
})),
233+
runStatsCommand: vi.fn(async () => ({
234+
totalLinks: 0,
235+
processedCount: 0,
236+
pendingCount: 0,
237+
failedCount: 0,
238+
})),
221239
isCliAvailable: vi.fn(async () => true),
222240
CliRunnerError: MockCliRunnerError,
223241
};
@@ -263,6 +281,12 @@ describe("index message handler integration", () => {
263281
url,
264282
summary: "Mock summary",
265283
})),
284+
runStatsCommand: vi.fn(async () => ({
285+
totalLinks: 0,
286+
processedCount: 0,
287+
pendingCount: 0,
288+
failedCount: 0,
289+
})),
266290
isCliAvailable: vi.fn(async () => true),
267291
CliRunnerError: MockCliRunnerError,
268292
};
@@ -309,6 +333,12 @@ describe("index message handler integration", () => {
309333
runAddCommand: vi.fn(),
310334
runQueueCommand: runQueueCommandMock,
311335
runSummaryCommand: vi.fn(),
336+
runStatsCommand: vi.fn(async () => ({
337+
totalLinks: 0,
338+
processedCount: 0,
339+
pendingCount: 0,
340+
failedCount: 0,
341+
})),
312342
runCliCommand: vi.fn(),
313343
isCliAvailable: vi.fn(async () => true),
314344
CliRunnerError: MockCliRunnerError,
@@ -366,6 +396,12 @@ describe("index message handler integration", () => {
366396
url,
367397
summary: "Mock summary",
368398
})),
399+
runStatsCommand: vi.fn(async () => ({
400+
totalLinks: 0,
401+
processedCount: 0,
402+
pendingCount: 0,
403+
failedCount: 0,
404+
})),
369405
isCliAvailable: vi.fn(async () => true),
370406
CliRunnerError: MockCliRunnerError,
371407
};
@@ -477,6 +513,12 @@ describe("index message handler integration", () => {
477513
runAddCommand: runAddCommandMock,
478514
runQueueCommand: vi.fn(),
479515
runSummaryCommand: runSummaryCommandMock,
516+
runStatsCommand: vi.fn(async () => ({
517+
totalLinks: 0,
518+
processedCount: 0,
519+
pendingCount: 0,
520+
failedCount: 0,
521+
})),
480522
isCliAvailable: vi.fn(async () => true),
481523
CliRunnerError: MockCliRunnerError,
482524
};
@@ -543,6 +585,12 @@ describe("index message handler integration", () => {
543585
runAddCommand: runAddCommandMock,
544586
runQueueCommand: vi.fn(),
545587
runSummaryCommand: runSummaryCommandMock,
588+
runStatsCommand: vi.fn(async () => ({
589+
totalLinks: 0,
590+
processedCount: 0,
591+
pendingCount: 0,
592+
failedCount: 0,
593+
})),
546594
isCliAvailable: vi.fn(async () => true),
547595
CliRunnerError: MockCliRunnerError,
548596
};
@@ -599,6 +647,12 @@ describe("index message handler integration", () => {
599647
runAddCommand: runAddCommandMock,
600648
runQueueCommand: vi.fn(),
601649
runSummaryCommand: runSummaryCommandMock,
650+
runStatsCommand: vi.fn(async () => ({
651+
totalLinks: 0,
652+
processedCount: 0,
653+
pendingCount: 0,
654+
failedCount: 0,
655+
})),
602656
isCliAvailable: vi.fn(async () => true),
603657
CliRunnerError: MockCliRunnerError,
604658
};

0 commit comments

Comments
 (0)