diff --git a/src/config/config.plugin-validation.test.ts b/src/config/config.plugin-validation.test.ts index b26713bdc24..72b3b4680c8 100644 --- a/src/config/config.plugin-validation.test.ts +++ b/src/config/config.plugin-validation.test.ts @@ -41,10 +41,7 @@ describe("config plugin validation", () => { OPENCLAW_PLUGIN_MANIFEST_CACHE_MS: process.env.OPENCLAW_PLUGIN_MANIFEST_CACHE_MS, }; - const validateInSuite = (raw: unknown) => { - process.env.OPENCLAW_STATE_DIR = path.join(suiteHome, ".openclaw"); - return validateConfigObjectWithPlugins(raw); - }; + const validateInSuite = (raw: unknown) => validateConfigObjectWithPlugins(raw); beforeAll(async () => { fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-config-plugin-validation-")); @@ -70,6 +67,7 @@ describe("config plugin validation", () => { channels: ["bluebubbles"], schema: { type: "object" }, }); + process.env.OPENCLAW_STATE_DIR = path.join(suiteHome, ".openclaw"); process.env.OPENCLAW_PLUGIN_MANIFEST_CACHE_MS = "10000"; clearPluginManifestRegistryCache(); }); diff --git a/src/cron/service.issue-regressions.test.ts b/src/cron/service.issue-regressions.test.ts index 45fd83c61ad..1625de80faa 100644 --- a/src/cron/service.issue-regressions.test.ts +++ b/src/cron/service.issue-regressions.test.ts @@ -38,7 +38,7 @@ function topOfHourOffsetMs(jobId: string) { let fixtureRoot = ""; let fixtureCount = 0; -async function makeStorePath() { +function makeStorePath() { const storePath = path.join(fixtureRoot, `case-${fixtureCount++}.jobs.json`); return { storePath, @@ -157,7 +157,6 @@ describe("Cron issue regressions", () => { }); beforeEach(() => { - vi.clearAllMocks(); vi.useFakeTimers(); vi.setSystemTime(new Date("2026-02-06T10:05:00.000Z")); }); @@ -168,7 +167,7 @@ describe("Cron issue regressions", () => { }); it("covers schedule updates and payload patching", async () => { - const store = await makeStorePath(); + const store = makeStorePath(); const cron = await startCronForStore({ storePath: store.storePath, cronEnabled: false, @@ -214,7 +213,7 @@ describe("Cron issue regressions", () => { }); it("repairs isolated every jobs missing createdAtMs and sets nextWakeAtMs", async () => { - const store = await makeStorePath(); + const store = makeStorePath(); await fs.writeFile( store.storePath, JSON.stringify({ @@ -263,7 +262,7 @@ describe("Cron issue regressions", () => { }); it("repairs missing nextRunAtMs on non-schedule updates without touching other jobs", async () => { - const store = await makeStorePath(); + const store = makeStorePath(); const cron = await startCronForStore({ storePath: store.storePath, cronEnabled: false }); const created = await cron.add({ @@ -287,7 +286,7 @@ describe("Cron issue regressions", () => { }); it("does not advance unrelated due jobs when updating another job", async () => { - const store = await makeStorePath(); + const store = makeStorePath(); const now = Date.parse("2026-02-06T10:05:00.000Z"); vi.setSystemTime(now); const cron = await startCronForStore({ storePath: store.storePath, cronEnabled: false }); @@ -329,7 +328,7 @@ describe("Cron issue regressions", () => { }); it("treats persisted jobs with missing enabled as enabled during update()", async () => { - const store = await makeStorePath(); + const store = makeStorePath(); const now = Date.parse("2026-02-06T10:05:00.000Z"); await fs.writeFile( store.storePath, @@ -372,7 +371,7 @@ describe("Cron issue regressions", () => { }); it("treats persisted due jobs with missing enabled as runnable", async () => { - const store = await makeStorePath(); + const store = makeStorePath(); const now = Date.parse("2026-02-06T10:05:00.000Z"); const dueAt = now - 30_000; await fs.writeFile( @@ -419,7 +418,7 @@ describe("Cron issue regressions", () => { it("caps timer delay to 60s for far-future schedules", async () => { const timeoutSpy = vi.spyOn(globalThis, "setTimeout"); - const store = await makeStorePath(); + const store = makeStorePath(); const cron = await startCronForStore({ storePath: store.storePath }); const callsBeforeAdd = timeoutSpy.mock.calls.length; @@ -444,7 +443,7 @@ describe("Cron issue regressions", () => { it("re-arms timer without hot-looping when a run is already in progress", async () => { const timeoutSpy = vi.spyOn(globalThis, "setTimeout"); - const store = await makeStorePath(); + const store = makeStorePath(); const now = Date.parse("2026-02-06T10:05:00.000Z"); const state = createRunningCronServiceState({ storePath: store.storePath, @@ -468,7 +467,7 @@ describe("Cron issue regressions", () => { }); it("skips forced manual runs while a timer-triggered run is in progress", async () => { - const store = await makeStorePath(); + const store = makeStorePath(); let resolveRun: | ((value: { status: "ok" | "error" | "skipped"; summary?: string; error?: string }) => void) | undefined; @@ -529,7 +528,7 @@ describe("Cron issue regressions", () => { }); it("does not double-run a job when cron.run overlaps a due timer tick", async () => { - const store = await makeStorePath(); + const store = makeStorePath(); const runStarted = createDeferred(); const runFinished = createDeferred(); const runResolvers: Array< @@ -586,7 +585,7 @@ describe("Cron issue regressions", () => { }); it("does not advance unrelated due jobs after manual cron.run", async () => { - const store = await makeStorePath(); + const store = makeStorePath(); const nowMs = Date.now(); const dueNextRunAtMs = nowMs - 1_000; @@ -627,7 +626,7 @@ describe("Cron issue regressions", () => { }); it("keeps telegram delivery target writeback after manual cron.run", async () => { - const store = await makeStorePath(); + const store = makeStorePath(); const originalTarget = "https://t.me/obviyus"; const rewrittenTarget = "-10012345/6789"; const runIsolatedAgentJob = vi.fn(async (params: { job: { id: string } }) => { @@ -675,7 +674,7 @@ describe("Cron issue regressions", () => { }); it("#13845: one-shot jobs with terminal statuses do not re-fire on restart", async () => { - const store = await makeStorePath(); + const store = makeStorePath(); const pastAt = Date.parse("2026-02-06T09:00:00.000Z"); const baseJob = { name: "reminder", @@ -732,7 +731,7 @@ describe("Cron issue regressions", () => { runIsolatedAgentJob: ReturnType; firstRetryAtMs: number; }> => { - const store = await makeStorePath(); + const store = makeStorePath(); const cronJob = createIsolatedRegressionJob({ id: params.id, name: "reminder", @@ -794,7 +793,7 @@ describe("Cron issue regressions", () => { }); it("#24355: one-shot job disabled after max transient retries", async () => { - const store = await makeStorePath(); + const store = makeStorePath(); const scheduledAt = Date.parse("2026-02-06T10:00:00.000Z"); const cronJob = createIsolatedRegressionJob({ @@ -837,7 +836,7 @@ describe("Cron issue regressions", () => { }); it("#24355: one-shot job respects cron.retry config", async () => { - const store = await makeStorePath(); + const store = makeStorePath(); const scheduledAt = Date.parse("2026-02-06T10:00:00.000Z"); const cronJob = createIsolatedRegressionJob({ @@ -883,7 +882,7 @@ describe("Cron issue regressions", () => { }); it("#24355: one-shot job disabled immediately on permanent error", async () => { - const store = await makeStorePath(); + const store = makeStorePath(); const scheduledAt = Date.parse("2026-02-06T10:00:00.000Z"); const cronJob = createIsolatedRegressionJob({ @@ -920,7 +919,7 @@ describe("Cron issue regressions", () => { }); it("prevents spin loop when cron job completes within the scheduled second (#17821)", async () => { - const store = await makeStorePath(); + const store = makeStorePath(); // Simulate a cron job "0 13 * * *" (daily 13:00 UTC) that fires exactly // at 13:00:00.000 and completes 7ms later (still in the same second). const scheduledAt = Date.parse("2026-02-15T13:00:00.000Z"); @@ -970,7 +969,7 @@ describe("Cron issue regressions", () => { }); it("enforces a minimum refire gap for second-granularity cron schedules (#17821)", async () => { - const store = await makeStorePath(); + const store = makeStorePath(); const scheduledAt = Date.parse("2026-02-15T13:00:00.000Z"); const cronJob = createIsolatedRegressionJob({ @@ -1008,7 +1007,7 @@ describe("Cron issue regressions", () => { }); it("treats timeoutSeconds=0 as no timeout for isolated agentTurn jobs", async () => { - const store = await makeStorePath(); + const store = makeStorePath(); const scheduledAt = Date.parse("2026-02-15T13:00:00.000Z"); const cronJob = createIsolatedRegressionJob({ @@ -1055,7 +1054,7 @@ describe("Cron issue regressions", () => { }); it("does not time out agentTurn jobs at the default 10-minute safety window", async () => { - const store = await makeStorePath(); + const store = makeStorePath(); const scheduledAt = Date.parse("2026-02-15T13:00:00.000Z"); const cronJob = createIsolatedRegressionJob({ @@ -1108,7 +1107,7 @@ describe("Cron issue regressions", () => { it("aborts isolated runs when cron timeout fires", async () => { vi.useRealTimers(); - const store = await makeStorePath(); + const store = makeStorePath(); const scheduledAt = Date.parse("2026-02-15T13:00:00.000Z"); const cronJob = createIsolatedRegressionJob({ id: "abort-on-timeout", @@ -1147,7 +1146,7 @@ describe("Cron issue regressions", () => { it("suppresses isolated follow-up side effects after timeout", async () => { vi.useRealTimers(); - const store = await makeStorePath(); + const store = makeStorePath(); const scheduledAt = Date.parse("2026-02-15T13:00:00.000Z"); const enqueueSystemEvent = vi.fn(); @@ -1201,7 +1200,7 @@ describe("Cron issue regressions", () => { it("applies timeoutSeconds to manual cron.run isolated executions", async () => { vi.useRealTimers(); - const store = await makeStorePath(); + const store = makeStorePath(); const abortAwareRunner = createAbortAwareIsolatedRunner(); const cron = await startCronForStore({ @@ -1237,7 +1236,7 @@ describe("Cron issue regressions", () => { it("applies timeoutSeconds to startup catch-up isolated executions", async () => { vi.useRealTimers(); - const store = await makeStorePath(); + const store = makeStorePath(); const scheduledAt = Date.parse("2026-02-15T13:00:00.000Z"); const cronJob = createIsolatedRegressionJob({ id: "startup-timeout", @@ -1354,7 +1353,7 @@ describe("Cron issue regressions", () => { }); it("records per-job start time and duration for batched due jobs", async () => { - const store = await makeStorePath(); + const store = makeStorePath(); const dueAt = Date.parse("2026-02-06T10:05:01.000Z"); const first = createDueIsolatedJob({ id: "batch-first", nowMs: dueAt, nextRunAtMs: dueAt }); const second = createDueIsolatedJob({ id: "batch-second", nowMs: dueAt, nextRunAtMs: dueAt }); @@ -1399,7 +1398,7 @@ describe("Cron issue regressions", () => { }); it("#17554: run() clears stale runningAtMs and executes the job", async () => { - const store = await makeStorePath(); + const store = makeStorePath(); const now = Date.parse("2026-02-06T10:05:00.000Z"); const staleRunningAtMs = now - 2 * 60 * 60 * 1000 - 1; @@ -1455,7 +1454,7 @@ describe("Cron issue regressions", () => { it("honors cron maxConcurrentRuns for due jobs", async () => { vi.useRealTimers(); - const store = await makeStorePath(); + const store = makeStorePath(); const dueAt = Date.parse("2026-02-06T10:05:01.000Z"); const first = createDueIsolatedJob({ id: "parallel-first", nowMs: dueAt, nextRunAtMs: dueAt }); const second = createDueIsolatedJob({ @@ -1528,7 +1527,7 @@ describe("Cron issue regressions", () => { // job abort that fires much sooner than the configured outer timeout. it("outer cron timeout fires at configured timeoutSeconds, not at 1/3 (#29774)", async () => { vi.useRealTimers(); - const store = await makeStorePath(); + const store = makeStorePath(); const scheduledAt = Date.parse("2026-02-15T13:00:00.000Z"); // Keep this short for suite speed while still separating expected timeout diff --git a/src/memory/qmd-manager.test.ts b/src/memory/qmd-manager.test.ts index 717257a5bdf..47814da7452 100644 --- a/src/memory/qmd-manager.test.ts +++ b/src/memory/qmd-manager.test.ts @@ -133,10 +133,9 @@ describe("QmdMemoryManager", () => { tmpRoot = path.join(fixtureRoot, `case-${fixtureCount++}`); workspaceDir = path.join(tmpRoot, "workspace"); stateDir = path.join(tmpRoot, "state"); - await Promise.all([ - fs.mkdir(workspaceDir, { recursive: true }), - fs.mkdir(stateDir, { recursive: true }), - ]); + // Only workspace must exist for configured collection paths; state paths are + // created lazily by manager code when needed. + await fs.mkdir(workspaceDir, { recursive: true }); process.env.OPENCLAW_STATE_DIR = stateDir; cfg = { agents: { diff --git a/src/slack/monitor/message-handler/prepare.test.ts b/src/slack/monitor/message-handler/prepare.test.ts index 7a20f5568b8..0db6a635303 100644 --- a/src/slack/monitor/message-handler/prepare.test.ts +++ b/src/slack/monitor/message-handler/prepare.test.ts @@ -104,6 +104,25 @@ describe("slack prepareSlackMessage inbound contract", () => { userTokenSource: "none", config: {}, }; + const defaultMessageTemplate: SlackMessageEvent = { + channel: "D123", + channel_type: "im", + user: "U1", + text: "hi", + ts: "1.000", + } as SlackMessageEvent; + const threadAccount: ResolvedSlackAccount = { + accountId: "default", + enabled: true, + botTokenSource: "config", + appTokenSource: "config", + userTokenSource: "none", + config: { + replyToMode: "all", + thread: { initialHistoryLimit: 20 }, + }, + replyToMode: "all", + }; async function prepareWithDefaultCtx(message: SlackMessageEvent) { return prepareSlackMessage({ @@ -129,14 +148,7 @@ describe("slack prepareSlackMessage inbound contract", () => { } function createSlackMessage(overrides: Partial): SlackMessageEvent { - return { - channel: "D123", - channel_type: "im", - user: "U1", - text: "hi", - ts: "1.000", - ...overrides, - } as SlackMessageEvent; + return { ...defaultMessageTemplate, ...overrides } as SlackMessageEvent; } async function prepareMessageWith( @@ -162,18 +174,7 @@ describe("slack prepareSlackMessage inbound contract", () => { } function createThreadAccount(): ResolvedSlackAccount { - return { - accountId: "default", - enabled: true, - botTokenSource: "config", - appTokenSource: "config", - userTokenSource: "none", - config: { - replyToMode: "all", - thread: { initialHistoryLimit: 20 }, - }, - replyToMode: "all", - }; + return threadAccount; } function createThreadReplyMessage(overrides: Partial): SlackMessageEvent {