File size: 4,057 Bytes
f778c12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
// Proves a fresh dev gateway can replace the synthetic implicit roster through real config IO.
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import { resetConfigRuntimeState } from "../../config/config.js";
import { withEnvAsync } from "../../test-utils/env.js";
import { nodeFilePath } from "../../test-utils/node-file-path.js";
import { ensureDevGatewayConfig } from "./dev.js";

describe("ensureDevGatewayConfig integration", () => {
  const tempDirs: string[] = [];

  afterEach(async () => {
    resetConfigRuntimeState();
    await Promise.all(
      tempDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true })),
    );
  });

  it("writes the dedicated dev roster into a fresh state directory", async () => {
    const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-dev-config-integration-"));
    tempDirs.push(root);
    const stateDir = path.join(root, "state");
    const configPath = path.join(stateDir, "openclaw.json");
    const workspace = path.join(root, "workspace");

    await withEnvAsync(
      {
        OPENCLAW_CONFIG_PATH: configPath,
        OPENCLAW_STATE_DIR: stateDir,
        OPENCLAW_WORKSPACE_DIR: workspace,
      },
      async () => {
        resetConfigRuntimeState();
        await ensureDevGatewayConfig({});
      },
    );

    const config = JSON.parse(await fs.readFile(configPath, "utf8")) as {
      agents?: { entries?: Record<string, { default?: boolean; workspace?: string }> };
    };
    expect(config.agents?.entries).toEqual({
      dev: { default: true, workspace: `${workspace}-dev`, identity: expect.any(Object) },
    });
  });

  it("can retry after a partial dev workspace write fails", async () => {
    const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-dev-config-integration-"));
    tempDirs.push(root);
    const stateDir = path.join(root, "state");
    const configPath = path.join(stateDir, "openclaw.json");
    const workspace = path.join(root, "workspace");
    const devWorkspace = `${workspace}-dev`;

    await withEnvAsync(
      {
        OPENCLAW_CONFIG_PATH: configPath,
        OPENCLAW_STATE_DIR: stateDir,
        OPENCLAW_WORKSPACE_DIR: workspace,
      },
      async () => {
        resetConfigRuntimeState();
        const realWriteFile = fs.writeFile.bind(fs);
        const writeSpy = vi
          .spyOn(fs, "writeFile")
          .mockImplementation(async (filePath, data, options) => {
            const rawPath = nodeFilePath(filePath);
            if (!rawPath) {
              return await realWriteFile(filePath, data, options);
            }
            const target = path.resolve(rawPath);
            const parent = path.dirname(target);
            const isStagedAgents =
              path.dirname(parent) === devWorkspace &&
              path.basename(parent).startsWith("openclaw-bootstrap-") &&
              path.basename(target) === "AGENTS.md";
            if (isStagedAgents) {
              await realWriteFile(filePath, "# PARTIAL\n", options);
              const error = new Error("ENOSPC") as NodeJS.ErrnoException;
              error.code = "ENOSPC";
              throw error;
            }
            return await realWriteFile(filePath, data, options);
          });

        try {
          await expect(ensureDevGatewayConfig({})).rejects.toMatchObject({ code: "ENOSPC" });
          await expect(fs.access(configPath)).rejects.toMatchObject({ code: "ENOENT" });
          await expect(fs.access(path.join(devWorkspace, "AGENTS.md"))).rejects.toMatchObject({
            code: "ENOENT",
          });
        } finally {
          writeSpy.mockRestore();
        }

        await ensureDevGatewayConfig({});
        const agents = await fs.readFile(path.join(devWorkspace, "AGENTS.md"), "utf8");
        expect(agents).toContain("gateway --dev");
        expect(agents).not.toBe("# PARTIAL\n");
        await expect(fs.access(configPath)).resolves.toBeUndefined();
      },
    );
  });
});