test: consolidate directive behavior suites for faster runs

This commit is contained in:
Peter Steinberger
2026-02-23 21:48:12 +00:00
parent b8fc8e7e6d
commit b9f01e8d3f
4 changed files with 226 additions and 278 deletions

View File

@@ -171,24 +171,18 @@ describe("directive behavior", () => {
expect(blockReplies.length).toBe(0); expect(blockReplies.length).toBe(0);
}); });
}); });
it("acks verbose directive immediately with system marker", async () => { it("handles standalone verbose directives and persistence", async () => {
await withTempHome(async (home) => { await withTempHome(async (home) => {
const res = await getReplyFromConfig( const storePath = sessionStorePath(home);
const enabledRes = await getReplyFromConfig(
{ Body: "/verbose on", From: "+1222", To: "+1222", CommandAuthorized: true }, { Body: "/verbose on", From: "+1222", To: "+1222", CommandAuthorized: true },
{}, {},
makeWhatsAppDirectiveConfig(home, { model: "anthropic/claude-opus-4-5" }), makeWhatsAppDirectiveConfig(home, { model: "anthropic/claude-opus-4-5" }),
); );
expect(replyText(enabledRes)).toMatch(/^⚙️ Verbose logging enabled\./);
const text = replyText(res); const disabledRes = await getReplyFromConfig(
expect(text).toMatch(/^⚙️ Verbose logging enabled\./);
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
});
});
it("persists verbose off when directive is standalone", async () => {
await withTempHome(async (home) => {
const storePath = sessionStorePath(home);
const res = await getReplyFromConfig(
{ Body: "/verbose off", From: "+1222", To: "+1222", CommandAuthorized: true }, { Body: "/verbose off", From: "+1222", To: "+1222", CommandAuthorized: true },
{}, {},
makeWhatsAppDirectiveConfig( makeWhatsAppDirectiveConfig(
@@ -200,7 +194,7 @@ describe("directive behavior", () => {
), ),
); );
const text = replyText(res); const text = replyText(disabledRes);
expect(text).toMatch(/Verbose logging disabled\./); expect(text).toMatch(/Verbose logging disabled\./);
const store = loadSessionStore(storePath); const store = loadSessionStore(storePath);
const entry = Object.values(store)[0]; const entry = Object.values(store)[0];
@@ -208,59 +202,46 @@ describe("directive behavior", () => {
expect(runEmbeddedPiAgent).not.toHaveBeenCalled(); expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
}); });
}); });
it("updates tool verbose during an in-flight run (toggle on)", async () => { it("updates tool verbose during in-flight runs for toggle on/off", async () => {
await withTempHome(async (home) => { await withTempHome(async (home) => {
const { res } = await runInFlightVerboseToggleCase({ for (const testCase of [
home, {
shouldEmitBefore: false, shouldEmitBefore: false,
toggledVerboseLevel: "on", toggledVerboseLevel: "on" as const,
}); },
{
const texts = replyTexts(res); shouldEmitBefore: true,
expect(texts).toContain("done"); toggledVerboseLevel: "off" as const,
expect(runEmbeddedPiAgent).toHaveBeenCalledOnce(); seedVerboseOn: true,
},
]) {
vi.mocked(runEmbeddedPiAgent).mockClear();
const { res } = await runInFlightVerboseToggleCase({
home,
...testCase,
});
const texts = replyTexts(res);
expect(texts).toContain("done");
expect(runEmbeddedPiAgent).toHaveBeenCalledOnce();
}
}); });
}); });
it("updates tool verbose during an in-flight run (toggle off)", async () => { it("covers think status and /thinking xhigh support matrix", async () => {
await withTempHome(async (home) => {
const { res } = await runInFlightVerboseToggleCase({
home,
shouldEmitBefore: true,
toggledVerboseLevel: "off",
seedVerboseOn: true,
});
const texts = replyTexts(res);
expect(texts).toContain("done");
expect(runEmbeddedPiAgent).toHaveBeenCalledOnce();
});
});
it("shows current think level when /think has no argument", async () => {
await withTempHome(async (home) => { await withTempHome(async (home) => {
const text = await runThinkDirectiveAndGetText(home); const text = await runThinkDirectiveAndGetText(home);
expect(text).toContain("Current thinking level: high"); expect(text).toContain("Current thinking level: high");
expect(text).toContain("Options: off, minimal, low, medium, high."); expect(text).toContain("Options: off, minimal, low, medium, high.");
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
}); for (const model of ["openai-codex/gpt-5.2-codex", "openai/gpt-5.2"]) {
}); const texts = await runThinkingDirective(home, model);
it("accepts /thinking xhigh for codex models", async () => { expect(texts).toContain("Thinking level set to xhigh.");
await withTempHome(async (home) => { }
const texts = await runThinkingDirective(home, "openai-codex/gpt-5.2-codex");
expect(texts).toContain("Thinking level set to xhigh."); const unsupportedModelTexts = await runThinkingDirective(home, "openai/gpt-4.1-mini");
}); expect(unsupportedModelTexts).toContain(
});
it("accepts /thinking xhigh for openai gpt-5.2", async () => {
await withTempHome(async (home) => {
const texts = await runThinkingDirective(home, "openai/gpt-5.2");
expect(texts).toContain("Thinking level set to xhigh.");
});
});
it("rejects /thinking xhigh for non-codex models", async () => {
await withTempHome(async (home) => {
const texts = await runThinkingDirective(home, "openai/gpt-4.1-mini");
expect(texts).toContain(
'Thinking level "xhigh" is only supported for openai/gpt-5.2, openai-codex/gpt-5.3-codex, openai-codex/gpt-5.3-codex-spark, openai-codex/gpt-5.2-codex, openai-codex/gpt-5.1-codex, github-copilot/gpt-5.2-codex or github-copilot/gpt-5.2.', 'Thinking level "xhigh" is only supported for openai/gpt-5.2, openai-codex/gpt-5.3-codex, openai-codex/gpt-5.3-codex-spark, openai-codex/gpt-5.2-codex, openai-codex/gpt-5.1-codex, github-copilot/gpt-5.2-codex or github-copilot/gpt-5.2.',
); );
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
}); });
}); });
it("keeps reserved command aliases from matching after trimming", async () => { it("keeps reserved command aliases from matching after trimming", async () => {
@@ -325,9 +306,9 @@ describe("directive behavior", () => {
expect(prompt).toContain('Use the "demo-skill" skill'); expect(prompt).toContain('Use the "demo-skill" skill');
}); });
}); });
it("errors on invalid queue options", async () => { it("reports invalid queue options and current queue settings", async () => {
await withTempHome(async (home) => { await withTempHome(async (home) => {
const res = await getReplyFromConfig( const invalidRes = await getReplyFromConfig(
{ {
Body: "/queue collect debounce:bogus cap:zero drop:maybe", Body: "/queue collect debounce:bogus cap:zero drop:maybe",
From: "+1222", From: "+1222",
@@ -344,16 +325,12 @@ describe("directive behavior", () => {
), ),
); );
const text = replyText(res); const invalidText = replyText(invalidRes);
expect(text).toContain("Invalid debounce"); expect(invalidText).toContain("Invalid debounce");
expect(text).toContain("Invalid cap"); expect(invalidText).toContain("Invalid cap");
expect(text).toContain("Invalid drop policy"); expect(invalidText).toContain("Invalid drop policy");
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
}); const currentRes = await getReplyFromConfig(
});
it("shows current queue settings when /queue has no arguments", async () => {
await withTempHome(async (home) => {
const res = await getReplyFromConfig(
{ {
Body: "/queue", Body: "/queue",
From: "+1222", From: "+1222",
@@ -379,7 +356,7 @@ describe("directive behavior", () => {
), ),
); );
const text = replyText(res); const text = replyText(currentRes);
expect(text).toContain( expect(text).toContain(
"Current queue settings: mode=collect, debounce=1500ms, cap=9, drop=summarize.", "Current queue settings: mode=collect, debounce=1500ms, cap=9, drop=summarize.",
); );

View File

@@ -86,6 +86,7 @@ async function runReasoningDefaultCase(params: {
expectedReasoningLevel: "off" | "on"; expectedReasoningLevel: "off" | "on";
thinkingDefault?: "off" | "low" | "medium" | "high"; thinkingDefault?: "off" | "low" | "medium" | "high";
}) { }) {
vi.mocked(runEmbeddedPiAgent).mockClear();
mockEmbeddedTextResult("done"); mockEmbeddedTextResult("done");
mockReasoningCapableCatalog(); mockReasoningCapableCatalog();
@@ -244,11 +245,11 @@ describe("directive behavior", () => {
expect(runEmbeddedPiAgent).not.toHaveBeenCalled(); expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
}); });
}); });
it("ignores inline /model and uses the default model", async () => { it("ignores inline /model and /think directives while still running agent content", async () => {
await withTempHome(async (home) => { await withTempHome(async (home) => {
mockEmbeddedTextResult("done"); mockEmbeddedTextResult("done");
const res = await getReplyFromConfig( const inlineModelRes = await getReplyFromConfig(
{ {
Body: "please sync /model openai/gpt-4.1-mini now", Body: "please sync /model openai/gpt-4.1-mini now",
From: "+1004", From: "+1004",
@@ -258,31 +259,47 @@ describe("directive behavior", () => {
makeDefaultModelConfig(home), makeDefaultModelConfig(home),
); );
const texts = replyTexts(res); const texts = replyTexts(inlineModelRes);
expect(texts).toContain("done"); expect(texts).toContain("done");
expect(runEmbeddedPiAgent).toHaveBeenCalledOnce(); expect(runEmbeddedPiAgent).toHaveBeenCalledOnce();
const call = vi.mocked(runEmbeddedPiAgent).mock.calls[0]?.[0]; const call = vi.mocked(runEmbeddedPiAgent).mock.calls[0]?.[0];
expect(call?.provider).toBe("anthropic"); expect(call?.provider).toBe("anthropic");
expect(call?.model).toBe("claude-opus-4-5"); expect(call?.model).toBe("claude-opus-4-5");
vi.mocked(runEmbeddedPiAgent).mockClear();
mockEmbeddedTextResult("done");
const inlineThinkRes = await getReplyFromConfig(
{
Body: "please sync /think:high now",
From: "+1004",
To: "+2000",
},
{},
makeWhatsAppDirectiveConfig(home, { model: { primary: "anthropic/claude-opus-4-5" } }),
);
expect(replyTexts(inlineThinkRes)).toContain("done");
expect(runEmbeddedPiAgent).toHaveBeenCalledOnce();
}); });
}); });
it("defaults thinking to low for reasoning-capable models without auto-enabling reasoning", async () => { it("applies reasoning defaults based on thinkingDefault configuration", async () => {
await withTempHome(async (home) => { await withTempHome(async (home) => {
await runReasoningDefaultCase({ for (const scenario of [
home, {
expectedThinkLevel: "low", expectedThinkLevel: "low" as const,
expectedReasoningLevel: "off", expectedReasoningLevel: "off" as const,
}); },
}); {
}); expectedThinkLevel: "off" as const,
it("keeps auto-reasoning enabled when thinking is explicitly off", async () => { expectedReasoningLevel: "on" as const,
await withTempHome(async (home) => { thinkingDefault: "off" as const,
await runReasoningDefaultCase({ },
home, ]) {
expectedThinkLevel: "off", await runReasoningDefaultCase({
expectedReasoningLevel: "on", home,
thinkingDefault: "off", ...scenario,
}); });
}
}); });
}); });
it("passes elevated defaults when sender is approved", async () => { it("passes elevated defaults when sender is approved", async () => {
@@ -384,17 +401,14 @@ describe("directive behavior", () => {
expect(call?.reasoningLevel).toBe("off"); expect(call?.reasoningLevel).toBe("off");
}); });
}); });
for (const replyTag of ["[[reply_to_current]]", "[[ reply_to_current ]]"]) { it("handles reply_to_current tags and explicit reply_to precedence", async () => {
it(`strips ${replyTag} and maps reply_to_current to MessageSid`, async () => { await withTempHome(async (home) => {
await withTempHome(async (home) => { for (const replyTag of ["[[reply_to_current]]", "[[ reply_to_current ]]"]) {
const payload = await runReplyToCurrentCase(home, `hello ${replyTag}`); const payload = await runReplyToCurrentCase(home, `hello ${replyTag}`);
expect(payload?.text).toBe("hello"); expect(payload?.text).toBe("hello");
expect(payload?.replyToId).toBe("msg-123"); expect(payload?.replyToId).toBe("msg-123");
}); }
});
}
it("prefers explicit reply_to id over reply_to_current", async () => {
await withTempHome(async (home) => {
vi.mocked(runEmbeddedPiAgent).mockResolvedValue( vi.mocked(runEmbeddedPiAgent).mockResolvedValue(
makeEmbeddedTextResult("hi [[reply_to_current]] [[reply_to:abc-456]]"), makeEmbeddedTextResult("hi [[reply_to_current]] [[reply_to:abc-456]]"),
); );
@@ -415,23 +429,4 @@ describe("directive behavior", () => {
expect(payload?.replyToId).toBe("abc-456"); expect(payload?.replyToId).toBe("abc-456");
}); });
}); });
it("applies inline think and still runs agent content", async () => {
await withTempHome(async (home) => {
mockEmbeddedTextResult("done");
const res = await getReplyFromConfig(
{
Body: "please sync /think:high now",
From: "+1004",
To: "+2000",
},
{},
makeWhatsAppDirectiveConfig(home, { model: { primary: "anthropic/claude-opus-4-5" } }),
);
const texts = replyTexts(res);
expect(texts).toContain("done");
expect(runEmbeddedPiAgent).toHaveBeenCalledOnce();
});
});
}); });

View File

@@ -110,87 +110,84 @@ describe("directive behavior", () => {
expect(runEmbeddedPiAgent).not.toHaveBeenCalled(); expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
}); });
}); });
it("picks the best fuzzy match when multiple models match", async () => { it("picks the best fuzzy match for global and provider-scoped minimax queries", async () => {
await withTempHome(async (home) => { await withTempHome(async (home) => {
const storePath = path.join(home, "sessions.json"); for (const testCase of [
await getReplyFromConfig(
{ Body: "/model minimax", From: "+1222", To: "+1222", CommandAuthorized: true },
{},
{ {
agents: { body: "/model minimax",
defaults: { storePath: path.join(home, "sessions-global-fuzzy.json"),
model: { primary: "minimax/MiniMax-M2.1" }, config: {
workspace: path.join(home, "openclaw"), agents: {
models: { defaults: {
"minimax/MiniMax-M2.1": {}, model: { primary: "minimax/MiniMax-M2.1" },
"minimax/MiniMax-M2.1-lightning": {}, workspace: path.join(home, "openclaw"),
"lmstudio/minimax-m2.1-gs32": {}, models: {
"minimax/MiniMax-M2.1": {},
"minimax/MiniMax-M2.1-lightning": {},
"lmstudio/minimax-m2.1-gs32": {},
},
},
},
models: {
mode: "merge",
providers: {
minimax: {
baseUrl: "https://api.minimax.io/anthropic",
apiKey: "sk-test",
api: "anthropic-messages",
models: [makeModelDefinition("MiniMax-M2.1", "MiniMax M2.1")],
},
lmstudio: {
baseUrl: "http://127.0.0.1:1234/v1",
apiKey: "lmstudio",
api: "openai-responses",
models: [makeModelDefinition("minimax-m2.1-gs32", "MiniMax M2.1 GS32")],
},
}, },
}, },
}, },
models: { },
mode: "merge",
providers: {
minimax: {
baseUrl: "https://api.minimax.io/anthropic",
apiKey: "sk-test",
api: "anthropic-messages",
models: [makeModelDefinition("MiniMax-M2.1", "MiniMax M2.1")],
},
lmstudio: {
baseUrl: "http://127.0.0.1:1234/v1",
apiKey: "lmstudio",
api: "openai-responses",
models: [makeModelDefinition("minimax-m2.1-gs32", "MiniMax M2.1 GS32")],
},
},
},
session: { store: storePath },
} as unknown as OpenClawConfig,
);
assertModelSelection(storePath);
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
});
});
it("picks the best fuzzy match within a provider", async () => {
await withTempHome(async (home) => {
const storePath = path.join(home, "sessions.json");
await getReplyFromConfig(
{ Body: "/model minimax/m2.1", From: "+1222", To: "+1222", CommandAuthorized: true },
{},
{ {
agents: { body: "/model minimax/m2.1",
defaults: { storePath: path.join(home, "sessions-provider-fuzzy.json"),
model: { primary: "minimax/MiniMax-M2.1" }, config: {
workspace: path.join(home, "openclaw"), agents: {
models: { defaults: {
"minimax/MiniMax-M2.1": {}, model: { primary: "minimax/MiniMax-M2.1" },
"minimax/MiniMax-M2.1-lightning": {}, workspace: path.join(home, "openclaw"),
models: {
"minimax/MiniMax-M2.1": {},
"minimax/MiniMax-M2.1-lightning": {},
},
},
},
models: {
mode: "merge",
providers: {
minimax: {
baseUrl: "https://api.minimax.io/anthropic",
apiKey: "sk-test",
api: "anthropic-messages",
models: [
makeModelDefinition("MiniMax-M2.1", "MiniMax M2.1"),
makeModelDefinition("MiniMax-M2.1-lightning", "MiniMax M2.1 Lightning"),
],
},
}, },
}, },
}, },
models: { },
mode: "merge", ]) {
providers: { await getReplyFromConfig(
minimax: { { Body: testCase.body, From: "+1222", To: "+1222", CommandAuthorized: true },
baseUrl: "https://api.minimax.io/anthropic", {},
apiKey: "sk-test", {
api: "anthropic-messages", ...testCase.config,
models: [ session: { store: testCase.storePath },
makeModelDefinition("MiniMax-M2.1", "MiniMax M2.1"), } as unknown as OpenClawConfig,
makeModelDefinition("MiniMax-M2.1-lightning", "MiniMax M2.1 Lightning"), );
], assertModelSelection(testCase.storePath);
}, }
},
},
session: { store: storePath },
} as unknown as OpenClawConfig,
);
assertModelSelection(storePath);
expect(runEmbeddedPiAgent).not.toHaveBeenCalled(); expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
}); });
}); });

View File

@@ -184,9 +184,9 @@ describe("directive behavior", () => {
expect(runEmbeddedPiAgent).not.toHaveBeenCalled(); expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
}); });
}); });
it("rejects per-agent elevated when disabled", async () => { it("enforces per-agent elevated restrictions and status visibility", async () => {
await withTempHome(async (home) => { await withTempHome(async (home) => {
const res = await getReplyFromConfig( const deniedRes = await getReplyFromConfig(
{ {
Body: "/elevated on", Body: "/elevated on",
From: "+1222", From: "+1222",
@@ -199,93 +199,10 @@ describe("directive behavior", () => {
{}, {},
makeRestrictedElevatedDisabledConfig(home) as unknown as OpenClawConfig, makeRestrictedElevatedDisabledConfig(home) as unknown as OpenClawConfig,
); );
const deniedText = replyText(deniedRes);
expect(deniedText).toContain("agents.list[].tools.elevated.enabled");
const text = replyText(res); const statusRes = await getReplyFromConfig(
expect(text).toContain("agents.list[].tools.elevated.enabled");
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
});
});
it("requires per-agent allowlist in addition to global", async () => {
await withTempHome(async (home) => {
const res = await getReplyFromConfig(
{
Body: "/elevated on",
From: "+1222",
To: "+1222",
Provider: "whatsapp",
SenderE164: "+1222",
SessionKey: "agent:work:main",
CommandAuthorized: true,
},
{},
makeWorkElevatedAllowlistConfig(home),
);
const text = replyText(res);
expect(text).toContain("agents.list[].tools.elevated.allowFrom.whatsapp");
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
});
});
it("allows elevated when both global and per-agent allowlists match", async () => {
await withTempHome(async (home) => {
const res = await getReplyFromConfig(
{
...makeCommandMessage("/elevated on", "+1333"),
SessionKey: "agent:work:main",
},
{},
makeWorkElevatedAllowlistConfig(home),
);
const text = replyText(res);
expect(text).toContain("Elevated mode set to ask");
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
});
});
it("warns when elevated is used in direct runtime", async () => {
await withTempHome(async (home) => {
const res = await getReplyFromConfig(
makeCommandMessage("/elevated off"),
{},
makeAllowlistedElevatedConfig(home, { sandbox: { mode: "off" } }),
);
const text = replyText(res);
expect(text).toContain("Elevated mode disabled.");
expect(text).toContain("Runtime is direct; sandboxing does not apply.");
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
});
});
it("rejects invalid elevated level", async () => {
await withTempHome(async (home) => {
const res = await getReplyFromConfig(
makeCommandMessage("/elevated maybe"),
{},
makeAllowlistedElevatedConfig(home),
);
const text = replyText(res);
expect(text).toContain("Unrecognized elevated level");
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
});
});
it("handles multiple directives in a single message", async () => {
await withTempHome(async (home) => {
const res = await getReplyFromConfig(
makeCommandMessage("/elevated off\n/verbose on"),
{},
makeAllowlistedElevatedConfig(home),
);
const text = replyText(res);
expect(text).toContain("Elevated mode disabled.");
expect(text).toContain("Verbose logging enabled.");
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
});
});
it("shows elevated off in status when per-agent elevated is disabled", async () => {
await withTempHome(async (home) => {
const res = await getReplyFromConfig(
{ {
Body: "/status", Body: "/status",
From: "+1222", From: "+1222",
@@ -298,9 +215,71 @@ describe("directive behavior", () => {
{}, {},
makeRestrictedElevatedDisabledConfig(home) as unknown as OpenClawConfig, makeRestrictedElevatedDisabledConfig(home) as unknown as OpenClawConfig,
); );
const statusText = replyText(statusRes);
expect(statusText).not.toContain("elevated");
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
});
});
it("applies per-agent allowlist requirements before allowing elevated", async () => {
await withTempHome(async (home) => {
const deniedRes = await getReplyFromConfig(
{
...makeCommandMessage("/elevated on", "+1222"),
SessionKey: "agent:work:main",
},
{},
makeWorkElevatedAllowlistConfig(home),
);
const text = replyText(res); const deniedText = replyText(deniedRes);
expect(text).not.toContain("elevated"); expect(deniedText).toContain("agents.list[].tools.elevated.allowFrom.whatsapp");
const allowedRes = await getReplyFromConfig(
{
...makeCommandMessage("/elevated on", "+1333"),
SessionKey: "agent:work:main",
},
{},
makeWorkElevatedAllowlistConfig(home),
);
const allowedText = replyText(allowedRes);
expect(allowedText).toContain("Elevated mode set to ask");
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
});
});
it("handles runtime warning, invalid level, and multi-directive elevated inputs", async () => {
await withTempHome(async (home) => {
for (const scenario of [
{
body: "/elevated off",
config: makeAllowlistedElevatedConfig(home, { sandbox: { mode: "off" } }),
expectedSnippets: [
"Elevated mode disabled.",
"Runtime is direct; sandboxing does not apply.",
],
},
{
body: "/elevated maybe",
config: makeAllowlistedElevatedConfig(home),
expectedSnippets: ["Unrecognized elevated level"],
},
{
body: "/elevated off\n/verbose on",
config: makeAllowlistedElevatedConfig(home),
expectedSnippets: ["Elevated mode disabled.", "Verbose logging enabled."],
},
]) {
const res = await getReplyFromConfig(
makeCommandMessage(scenario.body),
{},
scenario.config,
);
const text = replyText(res);
for (const snippet of scenario.expectedSnippets) {
expect(text).toContain(snippet);
}
}
expect(runEmbeddedPiAgent).not.toHaveBeenCalled(); expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
}); });
}); });