File size: 4,642 Bytes
d197cf3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import fs from "node:fs/promises";
import path from "node:path";
import type { DatabaseSync } from "node:sqlite";
import { afterEach, expect, it, vi } from "vitest";
import { useAutoCleanupTempDirTracker } from "../../test/helpers/temp-dir.js";
import {
  deleteSessionEntryLifecycle,
  replaceSessionEntry,
} from "../config/sessions/session-accessor.js";
import {
  createSqliteAuditRecordStore,
  registerSqliteAuditRecordAsync,
} from "../infra/sqlite-audit-record-store.js";
import type { RuntimeEnv } from "../runtime.js";
import {
  closeOpenClawAgentDatabaseByPathAsync,
  withOpenClawAgentDatabaseAsync,
} from "../state/openclaw-agent-db.js";
import {
  closeOpenClawStateDatabaseByPathAsync,
  openOpenClawStateDatabase,
} from "../state/openclaw-state-db.js";
import { agentExecCommand } from "./agent-exec.js";

const tempDirs = useAutoCleanupTempDirTracker(afterEach);

afterEach(() => vi.restoreAllMocks());

it.each([false, true])(
  "closes temporary databases before removal and preserves independent handles (run error: %s)",
  async (runError) => {
    const independentRoot = tempDirs.make("openclaw-agent-exec-independent-");
    const independent = openOpenClawStateDatabase({
      env: { OPENCLAW_STATE_DIR: independentRoot },
    });
    const previousStateDir = process.env.OPENCLAW_STATE_DIR;
    const runtime: RuntimeEnv = { log: vi.fn(), error: vi.fn(), exit: vi.fn() };
    let runStateDir: string | undefined;
    let agentPath: string | undefined;
    let statePath: string | undefined;
    const handles: DatabaseSync[] = [];
    const remove = fs.rm.bind(fs);
    const removed = vi.spyOn(fs, "rm").mockImplementation(async (pathname, options) => {
      if (pathname === runStateDir) {
        // Refuse the destructive step if the command has not settled native ownership.
        expect(handles).toHaveLength(2);
        expect(handles.every((handle) => !handle.isOpen)).toBe(true);
        expect(independent.db.isOpen).toBe(true);
      }
      await remove(pathname, options);
    });
    try {
      const result = await agentExecCommand("inspect", { authEnvOnly: true }, runtime, {
        baseConfig: { agents: { entries: { main: {} } } },
        runAgent: async () => {
          runStateDir = process.env.OPENCLAW_STATE_DIR;
          const shared = openOpenClawStateDatabase();
          handles.push(shared.db);
          statePath = shared.path;
          await withOpenClawAgentDatabaseAsync({ agentId: "main" }, (database) => {
            handles.push(database.db);
            agentPath = database.path;
          });
          const storePath = path.join(runStateDir!, "agents", "main", "sessions", "sessions.json");
          const sessionKey = "agent:main:exec-cleanup";
          await replaceSessionEntry(
            { sessionKey, storePath },
            { sessionId: "exec-cleanup", updatedAt: 1 },
          );
          const deletion = await deleteSessionEntryLifecycle({
            agentId: "main",
            storePath,
            target: { canonicalKey: sessionKey, storeKeys: [sessionKey] },
            archiveTranscript: false,
            deleteTranscriptWithoutArchive: true,
          });
          expect(deletion.deleted).toBe(true);
          await registerSqliteAuditRecordAsync(
            { scope: "agent-exec-cleanup", maxEntries: 1 },
            { key: "completed", value: "synthetic", createdAt: 1 },
          );
          expect(
            createSqliteAuditRecordStore({ scope: "agent-exec-cleanup", maxEntries: 1 }).entries(),
          ).toEqual([{ key: "completed", value: "synthetic", createdAt: 1 }]);
          if (runError) {
            throw new Error("Synthetic run failure");
          }
          return { payloads: [{ text: "done" }], meta: { durationMs: 1 } };
        },
      });
      expect(result.exitCode).toBe(runError ? 1 : 0);
      expect(runtime.error).not.toHaveBeenCalledWith(expect.stringContaining("cleanup failed"));
      expect(process.env.OPENCLAW_STATE_DIR).toBe(previousStateDir);
      expect(runStateDir).toBeDefined();
      await expect(fs.stat(runStateDir!)).rejects.toMatchObject({ code: "ENOENT" });
      expect(independent.db.prepare("SELECT 1 AS value").get()).toEqual({ value: 1 });
    } finally {
      removed.mockRestore();
      if (agentPath) {
        await closeOpenClawAgentDatabaseByPathAsync(agentPath);
      }
      if (statePath) {
        await closeOpenClawStateDatabaseByPathAsync(statePath);
      }
      await closeOpenClawStateDatabaseByPathAsync(independent.path);
      if (runStateDir) {
        await remove(runStateDir, { recursive: true, force: true });
      }
    }
  },
);