From 2b9a501b770fadc91c6a789da967c9ec7c0bd241 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 14 Feb 2026 19:55:04 +0000 Subject: [PATCH] refactor(test): dedupe directive behavior e2e setup --- ...ts-thinking-xhigh-codex-models.e2e.test.ts | 74 ++---------------- ...ixed-messages-acks-immediately.e2e.test.ts | 71 ++---------------- ...ow-reasoning-capable-models-no.e2e.test.ts | 68 +++-------------- ...irective.directive-behavior.e2e-harness.ts | 58 ++++++++++++++ ....directive.directive-behavior.e2e-mocks.ts | 14 ++++ ...nline-model-uses-default-model.e2e.test.ts | 66 +++------------- ...-allowlisted-models-model-list.e2e.test.ts | 74 +++--------------- ...s-fuzzy-selection-is-ambiguous.e2e.test.ts | 69 +++-------------- ...gent-allowlist-addition-global.e2e.test.ts | 75 ++----------------- ...-alongside-directive-only-acks.e2e.test.ts | 70 ++--------------- ...nt-elevated-level-as-off-after.e2e.test.ts | 68 ++--------------- ...t-verbose-level-verbose-has-no.e2e.test.ts | 73 ++---------------- ...-model-matches-model-directive.e2e.test.ts | 71 +++--------------- ...rbose-during-flight-run-toggle.e2e.test.ts | 66 ++-------------- 14 files changed, 175 insertions(+), 742 deletions(-) create mode 100644 src/auto-reply/reply.directive.directive-behavior.e2e-harness.ts create mode 100644 src/auto-reply/reply.directive.directive-behavior.e2e-mocks.ts diff --git a/src/auto-reply/reply.directive.directive-behavior.accepts-thinking-xhigh-codex-models.e2e.test.ts b/src/auto-reply/reply.directive.directive-behavior.accepts-thinking-xhigh-codex-models.e2e.test.ts index f94ba609242..783e1978440 100644 --- a/src/auto-reply/reply.directive.directive-behavior.accepts-thinking-xhigh-codex-models.e2e.test.ts +++ b/src/auto-reply/reply.directive.directive-behavior.accepts-thinking-xhigh-codex-models.e2e.test.ts @@ -1,14 +1,14 @@ +import "./reply.directive.directive-behavior.e2e-mocks.js"; import fs from "node:fs/promises"; import path from "node:path"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js"; -import { loadModelCatalog } from "../agents/model-catalog.js"; -import { runEmbeddedPiAgent } from "../agents/pi-embedded.js"; -import { loadSessionStore } from "../config/sessions.js"; +import { describe, expect, it, vi } from "vitest"; +import { + installDirectiveBehaviorE2EHooks, + runEmbeddedPiAgent, + withTempHome, +} from "./reply.directive.directive-behavior.e2e-harness.js"; import { getReplyFromConfig } from "./reply.js"; -const MAIN_SESSION_KEY = "agent:main:main"; - async function writeSkill(params: { workspaceDir: string; name: string; description: string }) { const { workspaceDir, name, description } = params; const skillDir = path.join(workspaceDir, "skills", name); @@ -20,57 +20,8 @@ async function writeSkill(params: { workspaceDir: string; name: string; descript ); } -vi.mock("../agents/pi-embedded.js", () => ({ - abortEmbeddedPiRun: vi.fn().mockReturnValue(false), - runEmbeddedPiAgent: vi.fn(), - queueEmbeddedPiMessage: vi.fn().mockReturnValue(false), - resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`, - isEmbeddedPiRunActive: vi.fn().mockReturnValue(false), - isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false), -})); -vi.mock("../agents/model-catalog.js", () => ({ - loadModelCatalog: vi.fn(), -})); - -async function withTempHome(fn: (home: string) => Promise): Promise { - return withTempHomeBase( - async (home) => { - return await fn(home); - }, - { - env: { - OPENCLAW_AGENT_DIR: (home) => path.join(home, ".openclaw", "agent"), - PI_CODING_AGENT_DIR: (home) => path.join(home, ".openclaw", "agent"), - }, - prefix: "openclaw-reply-", - }, - ); -} - -function _assertModelSelection( - storePath: string, - selection: { model?: string; provider?: string } = {}, -) { - const store = loadSessionStore(storePath); - const entry = store[MAIN_SESSION_KEY]; - expect(entry).toBeDefined(); - expect(entry?.modelOverride).toBe(selection.model); - expect(entry?.providerOverride).toBe(selection.provider); -} - describe("directive behavior", () => { - beforeEach(() => { - vi.mocked(runEmbeddedPiAgent).mockReset(); - vi.mocked(loadModelCatalog).mockResolvedValue([ - { id: "claude-opus-4-5", name: "Opus 4.5", provider: "anthropic" }, - { id: "claude-sonnet-4-1", name: "Sonnet 4.1", provider: "anthropic" }, - { id: "gpt-4.1-mini", name: "GPT-4.1 Mini", provider: "openai" }, - ]); - }); - - afterEach(() => { - vi.restoreAllMocks(); - }); + installDirectiveBehaviorE2EHooks(); it("accepts /thinking xhigh for codex models", async () => { await withTempHome(async (home) => { @@ -160,8 +111,6 @@ describe("directive behavior", () => { }); it("keeps reserved command aliases from matching after trimming", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); - const res = await getReplyFromConfig( { Body: "/help", @@ -192,7 +141,6 @@ describe("directive behavior", () => { }); it("treats skill commands as reserved for model aliases", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); const workspace = path.join(home, "openclaw"); await writeSkill({ workspaceDir: workspace, @@ -230,8 +178,6 @@ describe("directive behavior", () => { }); it("errors on invalid queue options", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); - const res = await getReplyFromConfig( { Body: "/queue collect debounce:bogus cap:zero drop:maybe", @@ -261,8 +207,6 @@ describe("directive behavior", () => { }); it("shows current queue settings when /queue has no arguments", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); - const res = await getReplyFromConfig( { Body: "/queue", @@ -304,8 +248,6 @@ describe("directive behavior", () => { }); it("shows current think level when /think has no argument", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); - const res = await getReplyFromConfig( { Body: "/think", From: "+1222", To: "+1222", CommandAuthorized: true }, {}, diff --git a/src/auto-reply/reply.directive.directive-behavior.applies-inline-reasoning-mixed-messages-acks-immediately.e2e.test.ts b/src/auto-reply/reply.directive.directive-behavior.applies-inline-reasoning-mixed-messages-acks-immediately.e2e.test.ts index 165d67a9314..b044ddd5a61 100644 --- a/src/auto-reply/reply.directive.directive-behavior.applies-inline-reasoning-mixed-messages-acks-immediately.e2e.test.ts +++ b/src/auto-reply/reply.directive.directive-behavior.applies-inline-reasoning-mixed-messages-acks-immediately.e2e.test.ts @@ -1,64 +1,16 @@ +import "./reply.directive.directive-behavior.e2e-mocks.js"; import path from "node:path"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js"; -import { loadModelCatalog } from "../agents/model-catalog.js"; -import { runEmbeddedPiAgent } from "../agents/pi-embedded.js"; +import { describe, expect, it, vi } from "vitest"; import { loadSessionStore } from "../config/sessions.js"; +import { + installDirectiveBehaviorE2EHooks, + runEmbeddedPiAgent, + withTempHome, +} from "./reply.directive.directive-behavior.e2e-harness.js"; import { getReplyFromConfig } from "./reply.js"; -const MAIN_SESSION_KEY = "agent:main:main"; - -vi.mock("../agents/pi-embedded.js", () => ({ - abortEmbeddedPiRun: vi.fn().mockReturnValue(false), - runEmbeddedPiAgent: vi.fn(), - queueEmbeddedPiMessage: vi.fn().mockReturnValue(false), - resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`, - isEmbeddedPiRunActive: vi.fn().mockReturnValue(false), - isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false), -})); -vi.mock("../agents/model-catalog.js", () => ({ - loadModelCatalog: vi.fn(), -})); - -async function withTempHome(fn: (home: string) => Promise): Promise { - return withTempHomeBase( - async (home) => { - return await fn(home); - }, - { - env: { - OPENCLAW_AGENT_DIR: (home) => path.join(home, ".openclaw", "agent"), - PI_CODING_AGENT_DIR: (home) => path.join(home, ".openclaw", "agent"), - }, - prefix: "openclaw-reply-", - }, - ); -} - -function _assertModelSelection( - storePath: string, - selection: { model?: string; provider?: string } = {}, -) { - const store = loadSessionStore(storePath); - const entry = store[MAIN_SESSION_KEY]; - expect(entry).toBeDefined(); - expect(entry?.modelOverride).toBe(selection.model); - expect(entry?.providerOverride).toBe(selection.provider); -} - describe("directive behavior", () => { - beforeEach(() => { - vi.mocked(runEmbeddedPiAgent).mockReset(); - vi.mocked(loadModelCatalog).mockResolvedValue([ - { id: "claude-opus-4-5", name: "Opus 4.5", provider: "anthropic" }, - { id: "claude-sonnet-4-1", name: "Sonnet 4.1", provider: "anthropic" }, - { id: "gpt-4.1-mini", name: "GPT-4.1 Mini", provider: "openai" }, - ]); - }); - - afterEach(() => { - vi.restoreAllMocks(); - }); + installDirectiveBehaviorE2EHooks(); it("applies inline reasoning in mixed messages and acks immediately", async () => { await withTempHome(async (home) => { @@ -176,8 +128,6 @@ describe("directive behavior", () => { }); it("acks verbose directive immediately with system marker", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); - const res = await getReplyFromConfig( { Body: "/verbose on", From: "+1222", To: "+1222", CommandAuthorized: true }, {}, @@ -199,7 +149,6 @@ describe("directive behavior", () => { }); it("persists verbose off when directive is standalone", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); const storePath = path.join(home, "sessions.json"); const res = await getReplyFromConfig( @@ -226,8 +175,6 @@ describe("directive behavior", () => { }); it("shows current think level when /think has no argument", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); - const res = await getReplyFromConfig( { Body: "/think", From: "+1222", To: "+1222", CommandAuthorized: true }, {}, @@ -251,8 +198,6 @@ describe("directive behavior", () => { }); it("shows off when /think has no argument and no default set", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); - const res = await getReplyFromConfig( { Body: "/think", From: "+1222", To: "+1222", CommandAuthorized: true }, {}, diff --git a/src/auto-reply/reply.directive.directive-behavior.defaults-think-low-reasoning-capable-models-no.e2e.test.ts b/src/auto-reply/reply.directive.directive-behavior.defaults-think-low-reasoning-capable-models-no.e2e.test.ts index 6bcaae9a030..14d0459cd90 100644 --- a/src/auto-reply/reply.directive.directive-behavior.defaults-think-low-reasoning-capable-models-no.e2e.test.ts +++ b/src/auto-reply/reply.directive.directive-behavior.defaults-think-low-reasoning-capable-models-no.e2e.test.ts @@ -1,68 +1,19 @@ +import "./reply.directive.directive-behavior.e2e-mocks.js"; import path from "node:path"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js"; -import { loadModelCatalog } from "../agents/model-catalog.js"; -import { runEmbeddedPiAgent } from "../agents/pi-embedded.js"; -import { loadSessionStore } from "../config/sessions.js"; +import { describe, expect, it, vi } from "vitest"; +import { + installDirectiveBehaviorE2EHooks, + loadModelCatalog, + runEmbeddedPiAgent, + withTempHome, +} from "./reply.directive.directive-behavior.e2e-harness.js"; import { getReplyFromConfig } from "./reply.js"; -const MAIN_SESSION_KEY = "agent:main:main"; - -vi.mock("../agents/pi-embedded.js", () => ({ - abortEmbeddedPiRun: vi.fn().mockReturnValue(false), - runEmbeddedPiAgent: vi.fn(), - queueEmbeddedPiMessage: vi.fn().mockReturnValue(false), - resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`, - isEmbeddedPiRunActive: vi.fn().mockReturnValue(false), - isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false), -})); -vi.mock("../agents/model-catalog.js", () => ({ - loadModelCatalog: vi.fn(), -})); - -async function withTempHome(fn: (home: string) => Promise): Promise { - return withTempHomeBase( - async (home) => { - return await fn(home); - }, - { - env: { - OPENCLAW_AGENT_DIR: (home) => path.join(home, ".openclaw", "agent"), - PI_CODING_AGENT_DIR: (home) => path.join(home, ".openclaw", "agent"), - }, - prefix: "openclaw-reply-", - }, - ); -} - -function _assertModelSelection( - storePath: string, - selection: { model?: string; provider?: string } = {}, -) { - const store = loadSessionStore(storePath); - const entry = store[MAIN_SESSION_KEY]; - expect(entry).toBeDefined(); - expect(entry?.modelOverride).toBe(selection.model); - expect(entry?.providerOverride).toBe(selection.provider); -} - describe("directive behavior", () => { - beforeEach(() => { - vi.mocked(runEmbeddedPiAgent).mockReset(); - vi.mocked(loadModelCatalog).mockResolvedValue([ - { id: "claude-opus-4-5", name: "Opus 4.5", provider: "anthropic" }, - { id: "claude-sonnet-4-1", name: "Sonnet 4.1", provider: "anthropic" }, - { id: "gpt-4.1-mini", name: "GPT-4.1 Mini", provider: "openai" }, - ]); - }); - - afterEach(() => { - vi.restoreAllMocks(); - }); + installDirectiveBehaviorE2EHooks(); it("defaults /think to low for reasoning-capable models when no default set", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); vi.mocked(loadModelCatalog).mockResolvedValueOnce([ { id: "claude-opus-4-5", @@ -94,7 +45,6 @@ describe("directive behavior", () => { }); it("shows off when /think has no argument and model lacks reasoning", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); vi.mocked(loadModelCatalog).mockResolvedValueOnce([ { id: "claude-opus-4-5", diff --git a/src/auto-reply/reply.directive.directive-behavior.e2e-harness.ts b/src/auto-reply/reply.directive.directive-behavior.e2e-harness.ts new file mode 100644 index 00000000000..d8bae56d819 --- /dev/null +++ b/src/auto-reply/reply.directive.directive-behavior.e2e-harness.ts @@ -0,0 +1,58 @@ +import path from "node:path"; +import { afterEach, beforeEach, expect, vi } from "vitest"; +import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js"; +import { loadModelCatalog } from "../agents/model-catalog.js"; +import { runEmbeddedPiAgent } from "../agents/pi-embedded.js"; +import { loadSessionStore } from "../config/sessions.js"; + +export { loadModelCatalog } from "../agents/model-catalog.js"; +export { runEmbeddedPiAgent } from "../agents/pi-embedded.js"; + +export const MAIN_SESSION_KEY = "agent:main:main"; + +export const DEFAULT_TEST_MODEL_CATALOG: Array<{ + id: string; + name: string; + provider: string; +}> = [ + { id: "claude-opus-4-5", name: "Opus 4.5", provider: "anthropic" }, + { id: "claude-sonnet-4-1", name: "Sonnet 4.1", provider: "anthropic" }, + { id: "gpt-4.1-mini", name: "GPT-4.1 Mini", provider: "openai" }, +]; + +export async function withTempHome(fn: (home: string) => Promise): Promise { + return withTempHomeBase( + async (home) => { + return await fn(home); + }, + { + env: { + OPENCLAW_AGENT_DIR: (home) => path.join(home, ".openclaw", "agent"), + PI_CODING_AGENT_DIR: (home) => path.join(home, ".openclaw", "agent"), + }, + prefix: "openclaw-reply-", + }, + ); +} + +export function assertModelSelection( + storePath: string, + selection: { model?: string; provider?: string } = {}, +) { + const store = loadSessionStore(storePath); + const entry = store[MAIN_SESSION_KEY]; + expect(entry).toBeDefined(); + expect(entry?.modelOverride).toBe(selection.model); + expect(entry?.providerOverride).toBe(selection.provider); +} + +export function installDirectiveBehaviorE2EHooks() { + beforeEach(() => { + vi.mocked(runEmbeddedPiAgent).mockReset(); + vi.mocked(loadModelCatalog).mockResolvedValue(DEFAULT_TEST_MODEL_CATALOG); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); +} diff --git a/src/auto-reply/reply.directive.directive-behavior.e2e-mocks.ts b/src/auto-reply/reply.directive.directive-behavior.e2e-mocks.ts new file mode 100644 index 00000000000..87849f1bf49 --- /dev/null +++ b/src/auto-reply/reply.directive.directive-behavior.e2e-mocks.ts @@ -0,0 +1,14 @@ +import { vi } from "vitest"; + +vi.mock("../agents/pi-embedded.js", () => ({ + abortEmbeddedPiRun: vi.fn().mockReturnValue(false), + runEmbeddedPiAgent: vi.fn(), + queueEmbeddedPiMessage: vi.fn().mockReturnValue(false), + resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`, + isEmbeddedPiRunActive: vi.fn().mockReturnValue(false), + isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false), +})); + +vi.mock("../agents/model-catalog.js", () => ({ + loadModelCatalog: vi.fn(), +})); diff --git a/src/auto-reply/reply.directive.directive-behavior.ignores-inline-model-uses-default-model.e2e.test.ts b/src/auto-reply/reply.directive.directive-behavior.ignores-inline-model-uses-default-model.e2e.test.ts index e3b676931dd..c9c3da75ab6 100644 --- a/src/auto-reply/reply.directive.directive-behavior.ignores-inline-model-uses-default-model.e2e.test.ts +++ b/src/auto-reply/reply.directive.directive-behavior.ignores-inline-model-uses-default-model.e2e.test.ts @@ -1,64 +1,16 @@ +import "./reply.directive.directive-behavior.e2e-mocks.js"; import path from "node:path"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js"; -import { loadModelCatalog } from "../agents/model-catalog.js"; -import { runEmbeddedPiAgent } from "../agents/pi-embedded.js"; -import { loadSessionStore } from "../config/sessions.js"; +import { describe, expect, it, vi } from "vitest"; +import { + installDirectiveBehaviorE2EHooks, + loadModelCatalog, + runEmbeddedPiAgent, + withTempHome, +} from "./reply.directive.directive-behavior.e2e-harness.js"; import { getReplyFromConfig } from "./reply.js"; -const MAIN_SESSION_KEY = "agent:main:main"; - -vi.mock("../agents/pi-embedded.js", () => ({ - abortEmbeddedPiRun: vi.fn().mockReturnValue(false), - runEmbeddedPiAgent: vi.fn(), - queueEmbeddedPiMessage: vi.fn().mockReturnValue(false), - resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`, - isEmbeddedPiRunActive: vi.fn().mockReturnValue(false), - isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false), -})); -vi.mock("../agents/model-catalog.js", () => ({ - loadModelCatalog: vi.fn(), -})); - -async function withTempHome(fn: (home: string) => Promise): Promise { - return withTempHomeBase( - async (home) => { - return await fn(home); - }, - { - env: { - OPENCLAW_AGENT_DIR: (home) => path.join(home, ".openclaw", "agent"), - PI_CODING_AGENT_DIR: (home) => path.join(home, ".openclaw", "agent"), - }, - prefix: "openclaw-reply-", - }, - ); -} - -function _assertModelSelection( - storePath: string, - selection: { model?: string; provider?: string } = {}, -) { - const store = loadSessionStore(storePath); - const entry = store[MAIN_SESSION_KEY]; - expect(entry).toBeDefined(); - expect(entry?.modelOverride).toBe(selection.model); - expect(entry?.providerOverride).toBe(selection.provider); -} - describe("directive behavior", () => { - beforeEach(() => { - vi.mocked(runEmbeddedPiAgent).mockReset(); - vi.mocked(loadModelCatalog).mockResolvedValue([ - { id: "claude-opus-4-5", name: "Opus 4.5", provider: "anthropic" }, - { id: "claude-sonnet-4-1", name: "Sonnet 4.1", provider: "anthropic" }, - { id: "gpt-4.1-mini", name: "GPT-4.1 Mini", provider: "openai" }, - ]); - }); - - afterEach(() => { - vi.restoreAllMocks(); - }); + installDirectiveBehaviorE2EHooks(); it("ignores inline /model and uses the default model", async () => { await withTempHome(async (home) => { diff --git a/src/auto-reply/reply.directive.directive-behavior.lists-allowlisted-models-model-list.e2e.test.ts b/src/auto-reply/reply.directive.directive-behavior.lists-allowlisted-models-model-list.e2e.test.ts index a18fab0277a..a66a476089b 100644 --- a/src/auto-reply/reply.directive.directive-behavior.lists-allowlisted-models-model-list.e2e.test.ts +++ b/src/auto-reply/reply.directive.directive-behavior.lists-allowlisted-models-model-list.e2e.test.ts @@ -1,68 +1,20 @@ +import "./reply.directive.directive-behavior.e2e-mocks.js"; import path from "node:path"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js"; -import { loadModelCatalog } from "../agents/model-catalog.js"; -import { runEmbeddedPiAgent } from "../agents/pi-embedded.js"; -import { loadSessionStore } from "../config/sessions.js"; +import { describe, expect, it, vi } from "vitest"; +import { + assertModelSelection, + installDirectiveBehaviorE2EHooks, + loadModelCatalog, + runEmbeddedPiAgent, + withTempHome, +} from "./reply.directive.directive-behavior.e2e-harness.js"; import { getReplyFromConfig } from "./reply.js"; -const MAIN_SESSION_KEY = "agent:main:main"; - -vi.mock("../agents/pi-embedded.js", () => ({ - abortEmbeddedPiRun: vi.fn().mockReturnValue(false), - runEmbeddedPiAgent: vi.fn(), - queueEmbeddedPiMessage: vi.fn().mockReturnValue(false), - resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`, - isEmbeddedPiRunActive: vi.fn().mockReturnValue(false), - isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false), -})); -vi.mock("../agents/model-catalog.js", () => ({ - loadModelCatalog: vi.fn(), -})); - -async function withTempHome(fn: (home: string) => Promise): Promise { - return withTempHomeBase( - async (home) => { - return await fn(home); - }, - { - env: { - OPENCLAW_AGENT_DIR: (home) => path.join(home, ".openclaw", "agent"), - PI_CODING_AGENT_DIR: (home) => path.join(home, ".openclaw", "agent"), - }, - prefix: "openclaw-reply-", - }, - ); -} - -function assertModelSelection( - storePath: string, - selection: { model?: string; provider?: string } = {}, -) { - const store = loadSessionStore(storePath); - const entry = store[MAIN_SESSION_KEY]; - expect(entry).toBeDefined(); - expect(entry?.modelOverride).toBe(selection.model); - expect(entry?.providerOverride).toBe(selection.provider); -} - describe("directive behavior", () => { - beforeEach(() => { - vi.mocked(runEmbeddedPiAgent).mockReset(); - vi.mocked(loadModelCatalog).mockResolvedValue([ - { id: "claude-opus-4-5", name: "Opus 4.5", provider: "anthropic" }, - { id: "claude-sonnet-4-1", name: "Sonnet 4.1", provider: "anthropic" }, - { id: "gpt-4.1-mini", name: "GPT-4.1 Mini", provider: "openai" }, - ]); - }); - - afterEach(() => { - vi.restoreAllMocks(); - }); + installDirectiveBehaviorE2EHooks(); it("aliases /model list to /models", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); const storePath = path.join(home, "sessions.json"); const res = await getReplyFromConfig( @@ -94,7 +46,6 @@ describe("directive behavior", () => { }); it("shows current model when catalog is unavailable", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); vi.mocked(loadModelCatalog).mockResolvedValueOnce([]); const storePath = path.join(home, "sessions.json"); @@ -126,7 +77,6 @@ describe("directive behavior", () => { }); it("includes catalog providers when no allowlist is set", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); vi.mocked(loadModelCatalog).mockResolvedValue([ { id: "claude-opus-4-5", name: "Opus 4.5", provider: "anthropic" }, { id: "gpt-4.1-mini", name: "GPT-4.1 Mini", provider: "openai" }, @@ -163,7 +113,6 @@ describe("directive behavior", () => { }); it("lists config-only providers when catalog is present", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); // Catalog present but missing custom providers: /model should still include // allowlisted provider/model keys from config. vi.mocked(loadModelCatalog).mockResolvedValueOnce([ @@ -213,7 +162,6 @@ describe("directive behavior", () => { }); it("does not repeat missing auth labels on /model list", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); const storePath = path.join(home, "sessions.json"); const res = await getReplyFromConfig( @@ -241,7 +189,6 @@ describe("directive behavior", () => { }); it("sets model override on /model directive", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); const storePath = path.join(home, "sessions.json"); await getReplyFromConfig( @@ -271,7 +218,6 @@ describe("directive behavior", () => { }); it("supports model aliases on /model directive", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); const storePath = path.join(home, "sessions.json"); await getReplyFromConfig( diff --git a/src/auto-reply/reply.directive.directive-behavior.prefers-alias-matches-fuzzy-selection-is-ambiguous.e2e.test.ts b/src/auto-reply/reply.directive.directive-behavior.prefers-alias-matches-fuzzy-selection-is-ambiguous.e2e.test.ts index f17fc2d589c..5e8b07315a4 100644 --- a/src/auto-reply/reply.directive.directive-behavior.prefers-alias-matches-fuzzy-selection-is-ambiguous.e2e.test.ts +++ b/src/auto-reply/reply.directive.directive-behavior.prefers-alias-matches-fuzzy-selection-is-ambiguous.e2e.test.ts @@ -1,70 +1,23 @@ +import "./reply.directive.directive-behavior.e2e-mocks.js"; import fs from "node:fs/promises"; import path from "node:path"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js"; -import { loadModelCatalog } from "../agents/model-catalog.js"; -import { runEmbeddedPiAgent } from "../agents/pi-embedded.js"; +import { describe, expect, it } from "vitest"; import { loadSessionStore } from "../config/sessions.js"; import { drainSystemEvents } from "../infra/system-events.js"; +import { + assertModelSelection, + installDirectiveBehaviorE2EHooks, + MAIN_SESSION_KEY, + runEmbeddedPiAgent, + withTempHome, +} from "./reply.directive.directive-behavior.e2e-harness.js"; import { getReplyFromConfig } from "./reply.js"; -const MAIN_SESSION_KEY = "agent:main:main"; - -vi.mock("../agents/pi-embedded.js", () => ({ - abortEmbeddedPiRun: vi.fn().mockReturnValue(false), - runEmbeddedPiAgent: vi.fn(), - queueEmbeddedPiMessage: vi.fn().mockReturnValue(false), - resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`, - isEmbeddedPiRunActive: vi.fn().mockReturnValue(false), - isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false), -})); -vi.mock("../agents/model-catalog.js", () => ({ - loadModelCatalog: vi.fn(), -})); - -async function withTempHome(fn: (home: string) => Promise): Promise { - return withTempHomeBase( - async (home) => { - return await fn(home); - }, - { - env: { - OPENCLAW_AGENT_DIR: (home) => path.join(home, ".openclaw", "agent"), - PI_CODING_AGENT_DIR: (home) => path.join(home, ".openclaw", "agent"), - }, - prefix: "openclaw-reply-", - }, - ); -} - -function assertModelSelection( - storePath: string, - selection: { model?: string; provider?: string } = {}, -) { - const store = loadSessionStore(storePath); - const entry = store[MAIN_SESSION_KEY]; - expect(entry).toBeDefined(); - expect(entry?.modelOverride).toBe(selection.model); - expect(entry?.providerOverride).toBe(selection.provider); -} - describe("directive behavior", () => { - beforeEach(() => { - vi.mocked(runEmbeddedPiAgent).mockReset(); - vi.mocked(loadModelCatalog).mockResolvedValue([ - { id: "claude-opus-4-5", name: "Opus 4.5", provider: "anthropic" }, - { id: "claude-sonnet-4-1", name: "Sonnet 4.1", provider: "anthropic" }, - { id: "gpt-4.1-mini", name: "GPT-4.1 Mini", provider: "openai" }, - ]); - }); - - afterEach(() => { - vi.restoreAllMocks(); - }); + installDirectiveBehaviorE2EHooks(); it("prefers alias matches when fuzzy selection is ambiguous", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); const storePath = path.join(home, "sessions.json"); const res = await getReplyFromConfig( @@ -114,7 +67,6 @@ describe("directive behavior", () => { }); it("stores auth profile overrides on /model directive", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); const storePath = path.join(home, "sessions.json"); const authDir = path.join(home, ".openclaw", "agents", "main", "agent"); await fs.mkdir(authDir, { recursive: true, mode: 0o700 }); @@ -165,7 +117,6 @@ describe("directive behavior", () => { it("queues a system event when switching models", async () => { await withTempHome(async (home) => { drainSystemEvents(MAIN_SESSION_KEY); - vi.mocked(runEmbeddedPiAgent).mockReset(); const storePath = path.join(home, "sessions.json"); await getReplyFromConfig( diff --git a/src/auto-reply/reply.directive.directive-behavior.requires-per-agent-allowlist-addition-global.e2e.test.ts b/src/auto-reply/reply.directive.directive-behavior.requires-per-agent-allowlist-addition-global.e2e.test.ts index ff0b42ff106..02293e1825c 100644 --- a/src/auto-reply/reply.directive.directive-behavior.requires-per-agent-allowlist-addition-global.e2e.test.ts +++ b/src/auto-reply/reply.directive.directive-behavior.requires-per-agent-allowlist-addition-global.e2e.test.ts @@ -1,69 +1,18 @@ +import "./reply.directive.directive-behavior.e2e-mocks.js"; import path from "node:path"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js"; -import { loadModelCatalog } from "../agents/model-catalog.js"; -import { runEmbeddedPiAgent } from "../agents/pi-embedded.js"; -import { loadSessionStore } from "../config/sessions.js"; +import { describe, expect, it } from "vitest"; +import { + installDirectiveBehaviorE2EHooks, + runEmbeddedPiAgent, + withTempHome, +} from "./reply.directive.directive-behavior.e2e-harness.js"; import { getReplyFromConfig } from "./reply.js"; -const MAIN_SESSION_KEY = "agent:main:main"; - -vi.mock("../agents/pi-embedded.js", () => ({ - abortEmbeddedPiRun: vi.fn().mockReturnValue(false), - runEmbeddedPiAgent: vi.fn(), - queueEmbeddedPiMessage: vi.fn().mockReturnValue(false), - resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`, - isEmbeddedPiRunActive: vi.fn().mockReturnValue(false), - isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false), -})); -vi.mock("../agents/model-catalog.js", () => ({ - loadModelCatalog: vi.fn(), -})); - -async function withTempHome(fn: (home: string) => Promise): Promise { - return withTempHomeBase( - async (home) => { - return await fn(home); - }, - { - env: { - OPENCLAW_AGENT_DIR: (home) => path.join(home, ".openclaw", "agent"), - PI_CODING_AGENT_DIR: (home) => path.join(home, ".openclaw", "agent"), - }, - prefix: "openclaw-reply-", - }, - ); -} - -function _assertModelSelection( - storePath: string, - selection: { model?: string; provider?: string } = {}, -) { - const store = loadSessionStore(storePath); - const entry = store[MAIN_SESSION_KEY]; - expect(entry).toBeDefined(); - expect(entry?.modelOverride).toBe(selection.model); - expect(entry?.providerOverride).toBe(selection.provider); -} - describe("directive behavior", () => { - beforeEach(() => { - vi.mocked(runEmbeddedPiAgent).mockReset(); - vi.mocked(loadModelCatalog).mockResolvedValue([ - { id: "claude-opus-4-5", name: "Opus 4.5", provider: "anthropic" }, - { id: "claude-sonnet-4-1", name: "Sonnet 4.1", provider: "anthropic" }, - { id: "gpt-4.1-mini", name: "GPT-4.1 Mini", provider: "openai" }, - ]); - }); - - afterEach(() => { - vi.restoreAllMocks(); - }); + installDirectiveBehaviorE2EHooks(); it("requires per-agent allowlist in addition to global", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); - const res = await getReplyFromConfig( { Body: "/elevated on", @@ -109,8 +58,6 @@ describe("directive behavior", () => { }); it("allows elevated when both global and per-agent allowlists match", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); - const res = await getReplyFromConfig( { Body: "/elevated on", @@ -156,8 +103,6 @@ describe("directive behavior", () => { }); it("warns when elevated is used in direct runtime", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); - const res = await getReplyFromConfig( { Body: "/elevated off", @@ -194,8 +139,6 @@ describe("directive behavior", () => { }); it("rejects invalid elevated level", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); - const res = await getReplyFromConfig( { Body: "/elevated maybe", @@ -230,8 +173,6 @@ describe("directive behavior", () => { }); it("handles multiple directives in a single message", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); - const res = await getReplyFromConfig( { Body: "/elevated off\n/verbose on", diff --git a/src/auto-reply/reply.directive.directive-behavior.returns-status-alongside-directive-only-acks.e2e.test.ts b/src/auto-reply/reply.directive.directive-behavior.returns-status-alongside-directive-only-acks.e2e.test.ts index cf41e85968e..d3749f7c369 100644 --- a/src/auto-reply/reply.directive.directive-behavior.returns-status-alongside-directive-only-acks.e2e.test.ts +++ b/src/auto-reply/reply.directive.directive-behavior.returns-status-alongside-directive-only-acks.e2e.test.ts @@ -1,68 +1,19 @@ +import "./reply.directive.directive-behavior.e2e-mocks.js"; import path from "node:path"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js"; -import { loadModelCatalog } from "../agents/model-catalog.js"; -import { runEmbeddedPiAgent } from "../agents/pi-embedded.js"; +import { describe, expect, it } from "vitest"; import { loadSessionStore } from "../config/sessions.js"; +import { + installDirectiveBehaviorE2EHooks, + runEmbeddedPiAgent, + withTempHome, +} from "./reply.directive.directive-behavior.e2e-harness.js"; import { getReplyFromConfig } from "./reply.js"; -const MAIN_SESSION_KEY = "agent:main:main"; - -vi.mock("../agents/pi-embedded.js", () => ({ - abortEmbeddedPiRun: vi.fn().mockReturnValue(false), - runEmbeddedPiAgent: vi.fn(), - queueEmbeddedPiMessage: vi.fn().mockReturnValue(false), - resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`, - isEmbeddedPiRunActive: vi.fn().mockReturnValue(false), - isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false), -})); -vi.mock("../agents/model-catalog.js", () => ({ - loadModelCatalog: vi.fn(), -})); - -async function withTempHome(fn: (home: string) => Promise): Promise { - return withTempHomeBase( - async (home) => { - return await fn(home); - }, - { - env: { - OPENCLAW_AGENT_DIR: (home) => path.join(home, ".openclaw", "agent"), - PI_CODING_AGENT_DIR: (home) => path.join(home, ".openclaw", "agent"), - }, - prefix: "openclaw-reply-", - }, - ); -} - -function _assertModelSelection( - storePath: string, - selection: { model?: string; provider?: string } = {}, -) { - const store = loadSessionStore(storePath); - const entry = store[MAIN_SESSION_KEY]; - expect(entry).toBeDefined(); - expect(entry?.modelOverride).toBe(selection.model); - expect(entry?.providerOverride).toBe(selection.provider); -} - describe("directive behavior", () => { - beforeEach(() => { - vi.mocked(runEmbeddedPiAgent).mockReset(); - vi.mocked(loadModelCatalog).mockResolvedValue([ - { id: "claude-opus-4-5", name: "Opus 4.5", provider: "anthropic" }, - { id: "claude-sonnet-4-1", name: "Sonnet 4.1", provider: "anthropic" }, - { id: "gpt-4.1-mini", name: "GPT-4.1 Mini", provider: "openai" }, - ]); - }); - - afterEach(() => { - vi.restoreAllMocks(); - }); + installDirectiveBehaviorE2EHooks(); it("returns status alongside directive-only acks", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); const storePath = path.join(home, "sessions.json"); const res = await getReplyFromConfig( @@ -106,8 +57,6 @@ describe("directive behavior", () => { }); it("shows elevated off in status when per-agent elevated is disabled", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); - const res = await getReplyFromConfig( { Body: "/status", @@ -151,7 +100,6 @@ describe("directive behavior", () => { }); it("acks queue directive and persists override", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); const storePath = path.join(home, "sessions.json"); const res = await getReplyFromConfig( @@ -179,7 +127,6 @@ describe("directive behavior", () => { }); it("persists queue options when directive is standalone", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); const storePath = path.join(home, "sessions.json"); const res = await getReplyFromConfig( @@ -218,7 +165,6 @@ describe("directive behavior", () => { }); it("resets queue mode to default", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); const storePath = path.join(home, "sessions.json"); await getReplyFromConfig( diff --git a/src/auto-reply/reply.directive.directive-behavior.shows-current-elevated-level-as-off-after.e2e.test.ts b/src/auto-reply/reply.directive.directive-behavior.shows-current-elevated-level-as-off-after.e2e.test.ts index 762dc0c3335..3db5742d37d 100644 --- a/src/auto-reply/reply.directive.directive-behavior.shows-current-elevated-level-as-off-after.e2e.test.ts +++ b/src/auto-reply/reply.directive.directive-behavior.shows-current-elevated-level-as-off-after.e2e.test.ts @@ -1,68 +1,19 @@ +import "./reply.directive.directive-behavior.e2e-mocks.js"; import path from "node:path"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js"; -import { loadModelCatalog } from "../agents/model-catalog.js"; -import { runEmbeddedPiAgent } from "../agents/pi-embedded.js"; +import { describe, expect, it } from "vitest"; import { loadSessionStore } from "../config/sessions.js"; +import { + installDirectiveBehaviorE2EHooks, + runEmbeddedPiAgent, + withTempHome, +} from "./reply.directive.directive-behavior.e2e-harness.js"; import { getReplyFromConfig } from "./reply.js"; -const MAIN_SESSION_KEY = "agent:main:main"; - -vi.mock("../agents/pi-embedded.js", () => ({ - abortEmbeddedPiRun: vi.fn().mockReturnValue(false), - runEmbeddedPiAgent: vi.fn(), - queueEmbeddedPiMessage: vi.fn().mockReturnValue(false), - resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`, - isEmbeddedPiRunActive: vi.fn().mockReturnValue(false), - isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false), -})); -vi.mock("../agents/model-catalog.js", () => ({ - loadModelCatalog: vi.fn(), -})); - -async function withTempHome(fn: (home: string) => Promise): Promise { - return withTempHomeBase( - async (home) => { - return await fn(home); - }, - { - env: { - OPENCLAW_AGENT_DIR: (home) => path.join(home, ".openclaw", "agent"), - PI_CODING_AGENT_DIR: (home) => path.join(home, ".openclaw", "agent"), - }, - prefix: "openclaw-reply-", - }, - ); -} - -function _assertModelSelection( - storePath: string, - selection: { model?: string; provider?: string } = {}, -) { - const store = loadSessionStore(storePath); - const entry = store[MAIN_SESSION_KEY]; - expect(entry).toBeDefined(); - expect(entry?.modelOverride).toBe(selection.model); - expect(entry?.providerOverride).toBe(selection.provider); -} - describe("directive behavior", () => { - beforeEach(() => { - vi.mocked(runEmbeddedPiAgent).mockReset(); - vi.mocked(loadModelCatalog).mockResolvedValue([ - { id: "claude-opus-4-5", name: "Opus 4.5", provider: "anthropic" }, - { id: "claude-sonnet-4-1", name: "Sonnet 4.1", provider: "anthropic" }, - { id: "gpt-4.1-mini", name: "GPT-4.1 Mini", provider: "openai" }, - ]); - }); - - afterEach(() => { - vi.restoreAllMocks(); - }); + installDirectiveBehaviorE2EHooks(); it("shows current elevated level as off after toggling it off", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); const storePath = path.join(home, "sessions.json"); await getReplyFromConfig( @@ -128,7 +79,6 @@ describe("directive behavior", () => { }); it("can toggle elevated off then back on (status reflects on)", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); const storePath = path.join(home, "sessions.json"); const cfg = { @@ -198,8 +148,6 @@ describe("directive behavior", () => { }); it("rejects per-agent elevated when disabled", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); - const res = await getReplyFromConfig( { Body: "/elevated on", diff --git a/src/auto-reply/reply.directive.directive-behavior.shows-current-verbose-level-verbose-has-no.e2e.test.ts b/src/auto-reply/reply.directive.directive-behavior.shows-current-verbose-level-verbose-has-no.e2e.test.ts index 891daca5fbe..df235dfb707 100644 --- a/src/auto-reply/reply.directive.directive-behavior.shows-current-verbose-level-verbose-has-no.e2e.test.ts +++ b/src/auto-reply/reply.directive.directive-behavior.shows-current-verbose-level-verbose-has-no.e2e.test.ts @@ -1,69 +1,19 @@ +import "./reply.directive.directive-behavior.e2e-mocks.js"; import path from "node:path"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js"; -import { loadModelCatalog } from "../agents/model-catalog.js"; -import { runEmbeddedPiAgent } from "../agents/pi-embedded.js"; +import { describe, expect, it, vi } from "vitest"; import { loadSessionStore } from "../config/sessions.js"; +import { + installDirectiveBehaviorE2EHooks, + runEmbeddedPiAgent, + withTempHome, +} from "./reply.directive.directive-behavior.e2e-harness.js"; import { getReplyFromConfig } from "./reply.js"; -const MAIN_SESSION_KEY = "agent:main:main"; - -vi.mock("../agents/pi-embedded.js", () => ({ - abortEmbeddedPiRun: vi.fn().mockReturnValue(false), - runEmbeddedPiAgent: vi.fn(), - queueEmbeddedPiMessage: vi.fn().mockReturnValue(false), - resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`, - isEmbeddedPiRunActive: vi.fn().mockReturnValue(false), - isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false), -})); -vi.mock("../agents/model-catalog.js", () => ({ - loadModelCatalog: vi.fn(), -})); - -async function withTempHome(fn: (home: string) => Promise): Promise { - return withTempHomeBase( - async (home) => { - return await fn(home); - }, - { - env: { - OPENCLAW_AGENT_DIR: (home) => path.join(home, ".openclaw", "agent"), - PI_CODING_AGENT_DIR: (home) => path.join(home, ".openclaw", "agent"), - }, - prefix: "openclaw-reply-", - }, - ); -} - -function _assertModelSelection( - storePath: string, - selection: { model?: string; provider?: string } = {}, -) { - const store = loadSessionStore(storePath); - const entry = store[MAIN_SESSION_KEY]; - expect(entry).toBeDefined(); - expect(entry?.modelOverride).toBe(selection.model); - expect(entry?.providerOverride).toBe(selection.provider); -} - describe("directive behavior", () => { - beforeEach(() => { - vi.mocked(runEmbeddedPiAgent).mockReset(); - vi.mocked(loadModelCatalog).mockResolvedValue([ - { id: "claude-opus-4-5", name: "Opus 4.5", provider: "anthropic" }, - { id: "claude-sonnet-4-1", name: "Sonnet 4.1", provider: "anthropic" }, - { id: "gpt-4.1-mini", name: "GPT-4.1 Mini", provider: "openai" }, - ]); - }); - - afterEach(() => { - vi.restoreAllMocks(); - }); + installDirectiveBehaviorE2EHooks(); it("shows current verbose level when /verbose has no argument", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); - const res = await getReplyFromConfig( { Body: "/verbose", From: "+1222", To: "+1222", CommandAuthorized: true }, {}, @@ -87,8 +37,6 @@ describe("directive behavior", () => { }); it("shows current reasoning level when /reasoning has no argument", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); - const res = await getReplyFromConfig( { Body: "/reasoning", From: "+1222", To: "+1222", CommandAuthorized: true }, {}, @@ -111,8 +59,6 @@ describe("directive behavior", () => { }); it("shows current elevated level when /elevated has no argument", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); - const res = await getReplyFromConfig( { Body: "/elevated", @@ -149,8 +95,6 @@ describe("directive behavior", () => { }); it("shows current exec defaults when /exec has no argument", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); - const res = await getReplyFromConfig( { Body: "/exec", @@ -190,7 +134,6 @@ describe("directive behavior", () => { }); it("persists elevated off and reflects it in /status (even when default is on)", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); const storePath = path.join(home, "sessions.json"); const res = await getReplyFromConfig( diff --git a/src/auto-reply/reply.directive.directive-behavior.supports-fuzzy-model-matches-model-directive.e2e.test.ts b/src/auto-reply/reply.directive.directive-behavior.supports-fuzzy-model-matches-model-directive.e2e.test.ts index 5a03484db6b..e67bbcf654e 100644 --- a/src/auto-reply/reply.directive.directive-behavior.supports-fuzzy-model-matches-model-directive.e2e.test.ts +++ b/src/auto-reply/reply.directive.directive-behavior.supports-fuzzy-model-matches-model-directive.e2e.test.ts @@ -1,68 +1,19 @@ +import "./reply.directive.directive-behavior.e2e-mocks.js"; import path from "node:path"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js"; -import { loadModelCatalog } from "../agents/model-catalog.js"; -import { runEmbeddedPiAgent } from "../agents/pi-embedded.js"; -import { loadSessionStore } from "../config/sessions.js"; +import { describe, expect, it } from "vitest"; +import { + assertModelSelection, + installDirectiveBehaviorE2EHooks, + runEmbeddedPiAgent, + withTempHome, +} from "./reply.directive.directive-behavior.e2e-harness.js"; import { getReplyFromConfig } from "./reply.js"; -const MAIN_SESSION_KEY = "agent:main:main"; - -vi.mock("../agents/pi-embedded.js", () => ({ - abortEmbeddedPiRun: vi.fn().mockReturnValue(false), - runEmbeddedPiAgent: vi.fn(), - queueEmbeddedPiMessage: vi.fn().mockReturnValue(false), - resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`, - isEmbeddedPiRunActive: vi.fn().mockReturnValue(false), - isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false), -})); -vi.mock("../agents/model-catalog.js", () => ({ - loadModelCatalog: vi.fn(), -})); - -async function withTempHome(fn: (home: string) => Promise): Promise { - return withTempHomeBase( - async (home) => { - return await fn(home); - }, - { - env: { - OPENCLAW_AGENT_DIR: (home) => path.join(home, ".openclaw", "agent"), - PI_CODING_AGENT_DIR: (home) => path.join(home, ".openclaw", "agent"), - }, - prefix: "openclaw-reply-", - }, - ); -} - -function assertModelSelection( - storePath: string, - selection: { model?: string; provider?: string } = {}, -) { - const store = loadSessionStore(storePath); - const entry = store[MAIN_SESSION_KEY]; - expect(entry).toBeDefined(); - expect(entry?.modelOverride).toBe(selection.model); - expect(entry?.providerOverride).toBe(selection.provider); -} - describe("directive behavior", () => { - beforeEach(() => { - vi.mocked(runEmbeddedPiAgent).mockReset(); - vi.mocked(loadModelCatalog).mockResolvedValue([ - { id: "claude-opus-4-5", name: "Opus 4.5", provider: "anthropic" }, - { id: "claude-sonnet-4-1", name: "Sonnet 4.1", provider: "anthropic" }, - { id: "gpt-4.1-mini", name: "GPT-4.1 Mini", provider: "openai" }, - ]); - }); - - afterEach(() => { - vi.restoreAllMocks(); - }); + installDirectiveBehaviorE2EHooks(); it("supports fuzzy model matches on /model directive", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); const storePath = path.join(home, "sessions.json"); const res = await getReplyFromConfig( @@ -105,7 +56,6 @@ describe("directive behavior", () => { }); it("resolves provider-less exact model ids via fuzzy matching when unambiguous", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); const storePath = path.join(home, "sessions.json"); const res = await getReplyFromConfig( @@ -153,7 +103,6 @@ describe("directive behavior", () => { }); it("supports fuzzy matches within a provider on /model provider/model", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); const storePath = path.join(home, "sessions.json"); const res = await getReplyFromConfig( @@ -196,7 +145,6 @@ describe("directive behavior", () => { }); it("picks the best fuzzy match when multiple models match", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); const storePath = path.join(home, "sessions.json"); await getReplyFromConfig( @@ -241,7 +189,6 @@ describe("directive behavior", () => { }); it("picks the best fuzzy match within a provider", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); const storePath = path.join(home, "sessions.json"); await getReplyFromConfig( diff --git a/src/auto-reply/reply.directive.directive-behavior.updates-tool-verbose-during-flight-run-toggle.e2e.test.ts b/src/auto-reply/reply.directive.directive-behavior.updates-tool-verbose-during-flight-run-toggle.e2e.test.ts index 687580c6aca..9afbaaae3ae 100644 --- a/src/auto-reply/reply.directive.directive-behavior.updates-tool-verbose-during-flight-run-toggle.e2e.test.ts +++ b/src/auto-reply/reply.directive.directive-behavior.updates-tool-verbose-during-flight-run-toggle.e2e.test.ts @@ -1,64 +1,16 @@ +import "./reply.directive.directive-behavior.e2e-mocks.js"; import path from "node:path"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js"; -import { loadModelCatalog } from "../agents/model-catalog.js"; -import { runEmbeddedPiAgent } from "../agents/pi-embedded.js"; +import { describe, expect, it, vi } from "vitest"; import { loadSessionStore, resolveSessionKey, saveSessionStore } from "../config/sessions.js"; +import { + installDirectiveBehaviorE2EHooks, + runEmbeddedPiAgent, + withTempHome, +} from "./reply.directive.directive-behavior.e2e-harness.js"; import { getReplyFromConfig } from "./reply.js"; -const MAIN_SESSION_KEY = "agent:main:main"; - -vi.mock("../agents/pi-embedded.js", () => ({ - abortEmbeddedPiRun: vi.fn().mockReturnValue(false), - runEmbeddedPiAgent: vi.fn(), - queueEmbeddedPiMessage: vi.fn().mockReturnValue(false), - resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`, - isEmbeddedPiRunActive: vi.fn().mockReturnValue(false), - isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false), -})); -vi.mock("../agents/model-catalog.js", () => ({ - loadModelCatalog: vi.fn(), -})); - -async function withTempHome(fn: (home: string) => Promise): Promise { - return withTempHomeBase( - async (home) => { - return await fn(home); - }, - { - env: { - OPENCLAW_AGENT_DIR: (home) => path.join(home, ".openclaw", "agent"), - PI_CODING_AGENT_DIR: (home) => path.join(home, ".openclaw", "agent"), - }, - prefix: "openclaw-reply-", - }, - ); -} - -function _assertModelSelection( - storePath: string, - selection: { model?: string; provider?: string } = {}, -) { - const store = loadSessionStore(storePath); - const entry = store[MAIN_SESSION_KEY]; - expect(entry).toBeDefined(); - expect(entry?.modelOverride).toBe(selection.model); - expect(entry?.providerOverride).toBe(selection.provider); -} - describe("directive behavior", () => { - beforeEach(() => { - vi.mocked(runEmbeddedPiAgent).mockReset(); - vi.mocked(loadModelCatalog).mockResolvedValue([ - { id: "claude-opus-4-5", name: "Opus 4.5", provider: "anthropic" }, - { id: "claude-sonnet-4-1", name: "Sonnet 4.1", provider: "anthropic" }, - { id: "gpt-4.1-mini", name: "GPT-4.1 Mini", provider: "openai" }, - ]); - }); - - afterEach(() => { - vi.restoreAllMocks(); - }); + installDirectiveBehaviorE2EHooks(); it("updates tool verbose during an in-flight run (toggle on)", async () => { await withTempHome(async (home) => { @@ -189,7 +141,6 @@ describe("directive behavior", () => { }); it("shows summary on /model", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); const storePath = path.join(home, "sessions.json"); const res = await getReplyFromConfig( @@ -221,7 +172,6 @@ describe("directive behavior", () => { }); it("lists allowlisted models on /model status", async () => { await withTempHome(async (home) => { - vi.mocked(runEmbeddedPiAgent).mockReset(); const storePath = path.join(home, "sessions.json"); const res = await getReplyFromConfig(