import { beforeEach, describe, expect, it, vi } from "vitest" ;
import type { OpenClawConfig } from "../../config/config.js" ;
import type {
CostUsageSummary,
CostUsageTotals,
SessionCostSummary,
} from "../../infra/session-cost-usage.js" ;
import { handleFastCommand, handleUsageCommand } from "./commands-session.js" ;
import type { HandleCommandsParams } from "./commands-types.js" ;
const resolveSessionAgentIdMock = vi.hoisted(() => vi.fn(() => "main" ));
const loadSessionCostSummaryMock = vi.hoisted(() =>
vi.fn<() => Promise<SessionCostSummary | null >>(async () => null ),
);
const loadCostUsageSummaryMock = vi.hoisted(() =>
vi.fn<() => Promise<CostUsageSummary>>(async () => ({
updatedAt: 0 ,
days: 30 ,
daily: [],
totals: {
input: 0 ,
output: 0 ,
cacheRead: 0 ,
cacheWrite: 0 ,
totalTokens: 0 ,
totalCost: 0 ,
inputCost: 0 ,
outputCost: 0 ,
cacheReadCost: 0 ,
cacheWriteCost: 0 ,
missingCostEntries: 0 ,
},
})),
);
const resolveFastModeStateMock = vi.hoisted(() =>
vi.fn(() => ({ enabled: true , source: "agent" })),
);
vi.mock("../../agents/agent-scope.js" , async () => {
const actual = await vi.importActual<typeof import ("../../agents/agent-scope.js" )>(
"../../agents/agent-scope.js" ,
);
return {
...actual,
resolveSessionAgentId: resolveSessionAgentIdMock,
};
});
vi.mock("../../infra/session-cost-usage.js" , () => ({
loadSessionCostSummary: loadSessionCostSummaryMock,
loadCostUsageSummary: loadCostUsageSummaryMock,
}));
vi.mock("../../agents/fast-mode.js" , () => ({
resolveFastModeState: resolveFastModeStateMock,
}));
function buildUsageParams(): HandleCommandsParams {
return {
cfg: {
commands: { text: true },
channels: { whatsapp: { allowFrom: ["*" ] } },
} as OpenClawConfig,
ctx: {
Provider: "whatsapp" ,
Surface: "whatsapp" ,
CommandSource: "text" ,
},
command: {
commandBodyNormalized: "/usage cost" ,
isAuthorizedSender: true ,
senderIsOwner: true ,
senderId: "owner" ,
channel: "whatsapp" ,
channelId: "whatsapp" ,
surface: "whatsapp" ,
ownerList: [],
from: "owner" ,
to: "bot" ,
},
sessionKey: "agent:target:whatsapp:direct:12345" ,
agentId: "main" ,
sessionEntry: {
sessionId: "session-1" ,
updatedAt: Date.now(),
},
} as unknown as HandleCommandsParams;
}
function buildCostTotals(overrides: Partial<CostUsageTotals> = {}): CostUsageTotals {
return {
input: 0 ,
output: 0 ,
cacheRead: 0 ,
cacheWrite: 0 ,
totalTokens: 0 ,
totalCost: 0 ,
inputCost: 0 ,
outputCost: 0 ,
cacheReadCost: 0 ,
cacheWriteCost: 0 ,
missingCostEntries: 0 ,
...overrides,
};
}
describe("handleUsageCommand" , () => {
beforeEach(() => {
vi.clearAllMocks();
resolveSessionAgentIdMock.mockReturnValue("target" );
loadSessionCostSummaryMock.mockResolvedValue({
...buildCostTotals({
totalCost: 1 .23 ,
totalTokens: 100 ,
missingCostEntries: 0 ,
}),
});
loadCostUsageSummaryMock.mockResolvedValue({
updatedAt: 0 ,
days: 30 ,
daily: [],
totals: buildCostTotals({
totalCost: 4 .56 ,
missingCostEntries: 0 ,
}),
});
});
it("uses the canonical target session agent for /usage cost" , async () => {
const result = await handleUsageCommand(buildUsageParams(), true );
expect(result?.shouldContinue).toBe(false );
expect(loadSessionCostSummaryMock).toHaveBeenCalledWith(
expect.objectContaining({
agentId: "target" ,
sessionId: "session-1" ,
}),
);
});
it("prefers the target session entry from sessionStore for /usage cost" , async () => {
const params = buildUsageParams();
params.sessionEntry = {
sessionId: "wrapper-session" ,
sessionFile: "/tmp/wrapper-session.jsonl" ,
updatedAt: Date.now(),
};
params.sessionStore = {
[params.sessionKey]: {
sessionId: "target-session" ,
sessionFile: "/tmp/target-session.jsonl" ,
updatedAt: Date.now(),
},
};
await handleUsageCommand(params, true );
expect(loadSessionCostSummaryMock).toHaveBeenCalledWith(
expect.objectContaining({
sessionId: "target-session" ,
sessionFile: "/tmp/target-session.jsonl" ,
}),
);
});
it("prefers the target session entry from sessionStore for /usage footer mode" , async () => {
const params = buildUsageParams();
params.command.commandBodyNormalized = "/usage" ;
params.sessionEntry = {
sessionId: "wrapper-session" ,
updatedAt: Date.now(),
responseUsage: "off" ,
};
params.sessionStore = {
[params.sessionKey]: {
sessionId: "target-session" ,
updatedAt: Date.now(),
responseUsage: "tokens" ,
},
};
const result = await handleUsageCommand(params, true );
expect(result?.shouldContinue).toBe(false );
expect(result?.reply?.text).toBe("⚙️ Usage footer: full." );
});
});
describe("handleFastCommand" , () => {
beforeEach(() => {
vi.clearAllMocks();
resolveSessionAgentIdMock.mockReturnValue("target" );
resolveFastModeStateMock.mockReturnValue({ enabled: true , source: "agent" });
});
it("uses the canonical target session agent for /fast status" , async () => {
const params = buildUsageParams();
params.command.commandBodyNormalized = "/fast status" ;
params.provider = "openai" ;
params.model = "gpt-5.4" ;
const result = await handleFastCommand(params, true );
expect(result?.shouldContinue).toBe(false );
expect(resolveFastModeStateMock).toHaveBeenCalledWith(
expect.objectContaining({
agentId: "target" ,
provider: "openai" ,
model: "gpt-5.4" ,
}),
);
expect(result?.reply?.text).toContain("Current fast mode: on" );
});
it("prefers the target session entry from sessionStore for /fast status" , async () => {
const params = buildUsageParams();
params.command.commandBodyNormalized = "/fast status" ;
params.provider = "openai" ;
params.model = "gpt-5.4" ;
params.sessionEntry = {
sessionId: "wrapper-session" ,
updatedAt: Date.now(),
fastMode: false ,
};
params.sessionStore = {
[params.sessionKey]: {
sessionId: "target-session" ,
updatedAt: Date.now(),
fastMode: true ,
},
};
await handleFastCommand(params, true );
expect(resolveFastModeStateMock).toHaveBeenCalledWith(
expect.objectContaining({
sessionEntry: expect.objectContaining({
sessionId: "target-session" ,
fastMode: true ,
}),
}),
);
});
});
Messung V0.5 in Prozent C=97 H=97 G=96
¤ Dauer der Verarbeitung: 0.9 Sekunden
(vorverarbeitet am 2026-06-10)
¤
*© Formatika GbR, Deutschland