Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/JAVA/Openclaw/src/commands/   (KI Agentensystem Version 22©)  Datei vom 26.3.2026 mit Größe 15 kB image not shown  

Quelle  backup.test.ts

  Sprache: JAVA
 

Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { RuntimeEnv } from "../runtime.js";
import { createTempHomeEnv, type TempHomeEnv } from "../test-utils/temp-home.js";
import * as backupShared from "./backup-shared.js";
import {
  buildBackupArchiveRoot,
  encodeAbsolutePathForBackupArchive,
  resolveBackupPlanFromPaths,
  resolveBackupPlanFromDisk,
} from "./backup-shared.js";
import {
  backupVerifyCommandMock,
  createBackupTestRuntime,
  mockStateOnlyBackupPlan,
  resetBackupTempHome,
  tarCreateMock,
} from "./backup.test-support.js";

const { backupCreateCommand } = await import("./backup.js");

describe("backup commands", () => {
  let tempHome: TempHomeEnv;

  async function mockWorkspaceBackupPlan(stateDir: string, workspaceDir: string, nowMs: number) {
    vi.spyOn(backupShared, "resolveBackupPlanFromDisk").mockResolvedValue(
      await resolveBackupPlanFromPaths({
        stateDir,
        configPath: path.join(stateDir, "openclaw.json"),
        oauthDir: path.join(stateDir, "credentials"),
        workspaceDirs: [workspaceDir],
        includeWorkspace: true,
        configInsideState: true,
        oauthInsideState: true,
        nowMs,
      }),
    );
  }

  beforeAll(async () => {
    tempHome = await createTempHomeEnv("openclaw-backup-test-");
  });

  beforeEach(async () => {
    await resetBackupTempHome(tempHome);
    tarCreateMock.mockReset();
    tarCreateMock.mockImplementation(async ({ file }: { file: string }) => {
      await fs.writeFile(file, "archive-bytes", "utf8");
    });
    backupVerifyCommandMock.mockReset();
    backupVerifyCommandMock.mockResolvedValue({
      ok: true,
      archivePath: "/tmp/fake.tar.gz",
      archiveRoot: "fake",
      createdAt: new Date().toISOString(),
      runtimeVersion: "test",
      assetCount: 1,
      entryCount: 2,
    });
  });

  afterEach(async () => {
    vi.restoreAllMocks();
  });

  afterAll(async () => {
    await tempHome.restore();
  });

  async function withInvalidWorkspaceBackupConfig<T>(fn: (runtime: RuntimeEnv) => Promise<T>) {
    const stateDir = path.join(tempHome.home, ".openclaw");
    const configPath = path.join(tempHome.home, "custom-config.json");
    process.env.OPENCLAW_CONFIG_PATH = configPath;
    await fs.writeFile(path.join(stateDir, "openclaw.json"), JSON.stringify({}), "utf8");
    await fs.writeFile(configPath, '{"agents": { defaults: { workspace: ', "utf8");
    const runtime = createBackupTestRuntime();

    try {
      return await fn(runtime);
    } finally {
      delete process.env.OPENCLAW_CONFIG_PATH;
    }
  }

  function expectWorkspaceCoveredByState(
    plan: Awaited<ReturnType<typeof resolveBackupPlanFromDisk>>,
  ) {
    expect(plan.included).toHaveLength(1);
    expect(plan.included[0]?.kind).toBe("state");
    expect(plan.skipped).toEqual(
      expect.arrayContaining([expect.objectContaining({ kind: "workspace", reason: "covered" })]),
    );
  }

  it("collapses default config, credentials, and workspace into the state backup root", async () => {
    const stateDir = path.join(tempHome.home, ".openclaw");
    const configPath = path.join(stateDir, "openclaw.json");
    const oauthDir = path.join(stateDir, "credentials");
    const workspaceDir = path.join(stateDir, "workspace");
    await fs.writeFile(configPath, JSON.stringify({}), "utf8");
    await fs.mkdir(oauthDir, { recursive: true });
    await fs.writeFile(path.join(oauthDir, "oauth.json"), "{}", "utf8");
    await fs.mkdir(workspaceDir, { recursive: true });
    await fs.writeFile(path.join(workspaceDir, "SOUL.md"), "# soul\n", "utf8");

    const plan = await resolveBackupPlanFromPaths({
      stateDir,
      configPath,
      oauthDir,
      workspaceDirs: [workspaceDir],
      includeWorkspace: true,
      configInsideState: true,
      oauthInsideState: true,
      nowMs: 123,
    });
    expectWorkspaceCoveredByState(plan);
  });

  it("orders coverage checks by canonical path so symlinked workspaces do not duplicate state", async () => {
    if (process.platform === "win32") {
      return;
    }

    const stateDir = path.join(tempHome.home, ".openclaw");
    const workspaceDir = path.join(stateDir, "workspace");
    const symlinkDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-workspace-link-"));
    const workspaceLink = path.join(symlinkDir, "ws-link");
    try {
      await fs.mkdir(workspaceDir, { recursive: true });
      await fs.writeFile(path.join(workspaceDir, "SOUL.md"), "# soul\n", "utf8");
      await fs.symlink(workspaceDir, workspaceLink);
      const plan = await resolveBackupPlanFromPaths({
        stateDir,
        configPath: path.join(stateDir, "openclaw.json"),
        oauthDir: path.join(stateDir, "credentials"),
        workspaceDirs: [workspaceLink],
        includeWorkspace: true,
        configInsideState: true,
        oauthInsideState: true,
        nowMs: 123,
      });
      expectWorkspaceCoveredByState(plan);
    } finally {
      await fs.rm(symlinkDir, { recursive: true, force: true });
    }
  });

  it("creates an archive with a manifest and external workspace payload", async () => {
    const stateDir = path.join(tempHome.home, ".openclaw");
    const externalWorkspace = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-workspace-"));
    const configPath = path.join(tempHome.home, "custom-config.json");
    const backupDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-backups-"));
    let capturedManifest: {
      assets: Array<{ kind: string; archivePath: string }>;
    } | null = null;
    let capturedEntryPaths: string[] = [];
    let capturedOnWriteEntry: ((entry: { path: string }) => void) | null = null;
    try {
      process.env.OPENCLAW_CONFIG_PATH = configPath;
      await fs.writeFile(
        configPath,
        JSON.stringify({
          agents: {
            defaults: {
              workspace: externalWorkspace,
            },
          },
        }),
        "utf8",
      );
      await fs.writeFile(path.join(stateDir, "state.txt"), "state\n", "utf8");
      await fs.writeFile(path.join(externalWorkspace, "SOUL.md"), "# external\n", "utf8");

      const runtime = createBackupTestRuntime();

      const nowMs = Date.UTC(2026, 2, 9, 0, 0, 0);
      vi.spyOn(backupShared, "resolveBackupPlanFromDisk").mockResolvedValue(
        await resolveBackupPlanFromPaths({
          stateDir,
          configPath,
          oauthDir: path.join(stateDir, "credentials"),
          workspaceDirs: [externalWorkspace],
          includeWorkspace: true,
          configInsideState: false,
          oauthInsideState: true,
          nowMs,
        }),
      );
      tarCreateMock.mockImplementationOnce(
        async (
          options: { file: string; onWriteEntry?: (entry: { path: string }) => void },
          entryPaths: string[],
        ) => {
          capturedManifest = JSON.parse(await fs.readFile(entryPaths[0], "utf8")) as {
            assets: Array<{ kind: string; archivePath: string }>;
          };
          capturedEntryPaths = entryPaths;
          capturedOnWriteEntry = options.onWriteEntry ?? null;
          await fs.writeFile(options.file, "archive-bytes", "utf8");
        },
      );
      const result = await backupCreateCommand(runtime, {
        output: backupDir,
        includeWorkspace: true,
        nowMs,
      });

      expect(result.archivePath).toBe(
        path.join(backupDir, `${buildBackupArchiveRoot(nowMs)}.tar.gz`),
      );
      expect(capturedManifest).not.toBeNull();
      expect(capturedOnWriteEntry).not.toBeNull();
      const manifest = capturedManifest as unknown as {
        assets: Array<{ kind: string; archivePath: string }>;
      };
      const onWriteEntry = capturedOnWriteEntry as unknown as (entry: { path: string }) => void;
      expect(manifest.assets).toEqual(
        expect.arrayContaining([
          expect.objectContaining({ kind: "state" }),
          expect.objectContaining({ kind: "config" }),
          expect.objectContaining({ kind: "workspace" }),
        ]),
      );

      const stateAsset = result.assets.find((asset) => asset.kind === "state");
      const workspaceAsset = result.assets.find((asset) => asset.kind === "workspace");
      expect(stateAsset).toBeDefined();
      expect(workspaceAsset).toBeDefined();
      expect(capturedEntryPaths).toHaveLength(result.assets.length + 1);

      const manifestPath = capturedEntryPaths[0];
      const remappedManifestEntry = { path: manifestPath };
      onWriteEntry(remappedManifestEntry);
      expect(remappedManifestEntry.path).toBe(
        path.posix.join(buildBackupArchiveRoot(nowMs), "manifest.json"),
      );

      const remappedStateEntry = { path: stateAsset!.sourcePath };
      onWriteEntry(remappedStateEntry);
      expect(remappedStateEntry.path).toBe(
        path.posix.join(
          buildBackupArchiveRoot(nowMs),
          "payload",
          encodeAbsolutePathForBackupArchive(stateAsset!.sourcePath),
        ),
      );

      const remappedWorkspaceEntry = { path: workspaceAsset!.sourcePath };
      onWriteEntry(remappedWorkspaceEntry);
      expect(remappedWorkspaceEntry.path).toBe(
        path.posix.join(
          buildBackupArchiveRoot(nowMs),
          "payload",
          encodeAbsolutePathForBackupArchive(workspaceAsset!.sourcePath),
        ),
      );
    } finally {
      delete process.env.OPENCLAW_CONFIG_PATH;
      await fs.rm(externalWorkspace, { recursive: true, force: true });
      await fs.rm(backupDir, { recursive: true, force: true });
    }
  });

  it("rejects output paths that would be created inside a backed-up directory", async () => {
    const stateDir = path.join(tempHome.home, ".openclaw");
    await fs.writeFile(path.join(stateDir, "openclaw.json"), JSON.stringify({}), "utf8");

    const runtime = createBackupTestRuntime();
    await mockStateOnlyBackupPlan(stateDir);

    await expect(
      backupCreateCommand(runtime, {
        output: path.join(stateDir, "backups"),
      }),
    ).rejects.toThrow(/must not be written inside a source path/i);
  });

  it("rejects symlinked output paths even when intermediate directories do not exist yet", async () => {
    if (process.platform === "win32") {
      return;
    }

    const stateDir = path.join(tempHome.home, ".openclaw");
    const symlinkDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-backup-link-"));
    const symlinkPath = path.join(symlinkDir, "linked-state");
    try {
      await fs.writeFile(path.join(stateDir, "openclaw.json"), JSON.stringify({}), "utf8");
      await fs.symlink(stateDir, symlinkPath);

      const runtime = createBackupTestRuntime();
      await mockStateOnlyBackupPlan(stateDir);

      await expect(
        backupCreateCommand(runtime, {
          output: path.join(symlinkPath, "new", "subdir", "backup.tar.gz"),
        }),
      ).rejects.toThrow(/must not be written inside a source path/i);
    } finally {
      await fs.rm(symlinkDir, { recursive: true, force: true });
    }
  });

  it("falls back to the home directory when cwd is inside a backed-up source tree", async () => {
    const stateDir = path.join(tempHome.home, ".openclaw");
    const workspaceDir = path.join(stateDir, "workspace");
    await fs.writeFile(path.join(stateDir, "openclaw.json"), JSON.stringify({}), "utf8");
    await fs.mkdir(workspaceDir, { recursive: true });
    await fs.writeFile(path.join(workspaceDir, "SOUL.md"), "# soul\n", "utf8");
    vi.spyOn(process, "cwd").mockReturnValue(workspaceDir);
    const nowMs = Date.UTC(2026, 2, 9, 1, 2, 3);
    await mockWorkspaceBackupPlan(stateDir, workspaceDir, nowMs);

    const runtime = createBackupTestRuntime();

    const result = await backupCreateCommand(runtime, { nowMs });

    expect(result.archivePath).toBe(
      path.join(tempHome.home, `${buildBackupArchiveRoot(nowMs)}.tar.gz`),
    );
    await fs.rm(result.archivePath, { force: true });

    if (process.platform !== "win32") {
      const linkParent = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-backup-cwd-link-"));
      const workspaceLink = path.join(linkParent, "workspace-link");
      try {
        await fs.symlink(workspaceDir, workspaceLink);
        vi.mocked(process.cwd).mockReturnValue(workspaceLink);
        const symlinkNowMs = Date.UTC(2026, 2, 9, 1, 3, 4);
        await mockWorkspaceBackupPlan(stateDir, workspaceDir, symlinkNowMs);
        const symlinkResult = await backupCreateCommand(createBackupTestRuntime(), {
          nowMs: symlinkNowMs,
        });
        expect(symlinkResult.archivePath).toBe(
          path.join(tempHome.home, `${buildBackupArchiveRoot(symlinkNowMs)}.tar.gz`),
        );
        await fs.rm(symlinkResult.archivePath, { force: true });
      } finally {
        await fs.rm(linkParent, { recursive: true, force: true });
      }
    }
  });

  it("allows dry-run preview even when the target archive already exists", async () => {
    const stateDir = path.join(tempHome.home, ".openclaw");
    const existingArchive = path.join(tempHome.home, "existing-backup.tar.gz");
    await fs.writeFile(path.join(stateDir, "openclaw.json"), JSON.stringify({}), "utf8");
    await fs.writeFile(existingArchive, "already here", "utf8");
    vi.spyOn(backupShared, "resolveBackupPlanFromDisk").mockResolvedValue(
      await resolveBackupPlanFromPaths({
        stateDir,
        configPath: path.join(stateDir, "openclaw.json"),
        oauthDir: path.join(stateDir, "credentials"),
        includeWorkspace: false,
        configInsideState: true,
        oauthInsideState: true,
        nowMs: 123,
      }),
    );

    const runtime = createBackupTestRuntime();

    const result = await backupCreateCommand(runtime, {
      output: existingArchive,
      dryRun: true,
    });

    expect(result.dryRun).toBe(true);
    expect(result.verified).toBe(false);
    expect(result.archivePath).toBe(existingArchive);
    expect(await fs.readFile(existingArchive, "utf8")).toBe("already here");
  });

  it("handles invalid config according to backup scope", async () => {
    await withInvalidWorkspaceBackupConfig(async (runtime) => {
      await expect(backupCreateCommand(runtime, { dryRun: true })).rejects.toThrow(
        /--no-include-workspace/i,
      );

      const result = await backupCreateCommand(runtime, {
        dryRun: true,
        includeWorkspace: false,
      });

      expect(result.includeWorkspace).toBe(false);
      expect(result.assets.some((asset) => asset.kind === "workspace")).toBe(false);

      const configOnly = await backupCreateCommand(runtime, {
        dryRun: true,
        onlyConfig: true,
      });
      expect(configOnly.assets).toHaveLength(1);
      expect(configOnly.assets[0]?.kind).toBe("config");
    });
  });

  it("backs up only the active config file when --only-config is requested", async () => {
    const stateDir = path.join(tempHome.home, ".openclaw");
    const configPath = path.join(stateDir, "openclaw.json");
    await fs.mkdir(path.join(stateDir, "credentials"), { recursive: true });
    await fs.writeFile(configPath, JSON.stringify({ theme: "config-only" }), "utf8");
    await fs.writeFile(path.join(stateDir, "state.txt"), "state\n", "utf8");
    await fs.writeFile(path.join(stateDir, "credentials", "oauth.json"), "{}", "utf8");
    vi.spyOn(backupShared, "resolveBackupPlanFromDisk").mockResolvedValue(
      await resolveBackupPlanFromPaths({
        stateDir,
        configPath,
        oauthDir: path.join(stateDir, "credentials"),
        includeWorkspace: false,
        onlyConfig: true,
        configInsideState: true,
        oauthInsideState: true,
        nowMs: 123,
      }),
    );

    const runtime = createBackupTestRuntime();

    const result = await backupCreateCommand(runtime, {
      dryRun: true,
      onlyConfig: true,
    });

    expect(result.onlyConfig).toBe(true);
    expect(result.includeWorkspace).toBe(false);
    expect(result.assets).toHaveLength(1);
    expect(result.assets[0]?.kind).toBe("config");
  });
});

¤ Dauer der Verarbeitung: 0.20 Sekunden  (vorverarbeitet am  2026-04-27) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.