import fs from "node:fs/promises" ;
import path from "node:path" ;
import { afterEach, describe, expect, it, vi } from "vitest" ;
import { withTempDir } from "../test-helpers/temp-dir.js" ;
import * as archive from "./archive.js" ;
import { resolveExistingInstallPath, withExtractedArchiveRoot } from "./install-flow.js" ;
import * as installSource from "./install-source-utils.js" ;
async function runExtractedArchiveFailureCase(configureArchive: () => void ) {
vi.spyOn(installSource, "withTempDir" ).mockImplementation(
async (_prefix, fn) => await fn("/tmp/openclaw-install-flow" ),
);
configureArchive();
return await withExtractedArchiveRoot({
archivePath: "/tmp/plugin.tgz" ,
tempDirPrefix: "openclaw-plugin-" ,
timeoutMs: 1000 ,
onExtracted: async () => ({ ok: true as const }),
});
}
describe("resolveExistingInstallPath" , () => {
it("returns resolved path and stat for existing files" , async () => {
await withTempDir({ prefix: "openclaw-install-flow-" }, async (fixtureRoot) => {
const filePath = path.join(fixtureRoot, "plugin.tgz" );
await fs.writeFile(filePath, "archive" );
const result = await resolveExistingInstallPath(filePath);
expect(result.ok).toBe(true );
if (!result.ok) {
return ;
}
expect(result.resolvedPath).toBe(filePath);
expect(result.stat.isFile()).toBe(true );
});
});
it("returns a path-not-found error for missing paths" , async () => {
await withTempDir({ prefix: "openclaw-install-flow-" }, async (fixtureRoot) => {
const missing = path.join(fixtureRoot, "missing.tgz" );
const result = await resolveExistingInstallPath(missing);
expect(result).toEqual({
ok: false ,
error: `path not found: ${missing}`,
});
});
});
});
describe("withExtractedArchiveRoot" , () => {
afterEach(() => {
vi.restoreAllMocks();
});
it("extracts archive and passes root directory to callback" , async () => {
const tmpRoot = path.join(path.sep, "tmp" , "openclaw-install-flow" );
const archivePath = path.join(path.sep, "tmp" , "plugin.tgz" );
const extractDir = path.join(tmpRoot, "extract" );
const packageRoot = path.join(extractDir, "package" );
const withTempDirSpy = vi
.spyOn(installSource, "withTempDir" )
.mockImplementation(async (_prefix, fn) => await fn(tmpRoot));
const extractSpy = vi.spyOn(archive, "extractArchive" ).mockResolvedValue(undefined);
const resolveRootSpy = vi.spyOn(archive, "resolvePackedRootDir" ).mockResolvedValue(packageRoot);
const onExtracted = vi.fn(async (rootDir: string) => ({ ok: true as const , rootDir }));
const result = await withExtractedArchiveRoot({
archivePath,
tempDirPrefix: "openclaw-plugin-" ,
timeoutMs: 1000 ,
rootMarkers: ["package.json" ],
onExtracted,
});
expect(withTempDirSpy).toHaveBeenCalledWith("openclaw-plugin-" , expect.any(Function ));
expect(extractSpy).toHaveBeenCalledWith(
expect.objectContaining({
archivePath,
}),
);
expect(resolveRootSpy).toHaveBeenCalledWith(extractDir, {
rootMarkers: ["package.json" ],
});
expect(onExtracted).toHaveBeenCalledWith(packageRoot);
expect(result).toEqual({
ok: true ,
rootDir: packageRoot,
});
});
it("returns extract failure when extraction throws" , async () => {
const result = await runExtractedArchiveFailureCase(() => {
vi.spyOn(archive, "extractArchive" ).mockRejectedValue(new Error("boom" ));
});
expect(result).toEqual({
ok: false ,
error: "failed to extract archive: Error: boom" ,
});
});
it("returns root-resolution failure when archive layout is invalid" , async () => {
const result = await runExtractedArchiveFailureCase(() => {
vi.spyOn(archive, "extractArchive" ).mockResolvedValue(undefined);
vi.spyOn(archive, "resolvePackedRootDir" ).mockRejectedValue(new Error("invalid layout" ));
});
expect(result).toEqual({
ok: false ,
error: "Error: invalid layout" ,
});
});
});
Messung V0.5 in Prozent C=99 H=95 G=96
¤ Dauer der Verarbeitung: 0.4 Sekunden
¤
*© Formatika GbR, Deutschland