mirror of https://github.com/openclaw/openclaw.git
test: add install and pairing helper coverage
This commit is contained in:
parent
60d308cff0
commit
1fefd4e67f
|
|
@ -0,0 +1,77 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
const validateRegistryNpmSpecMock = vi.hoisted(() => vi.fn());
|
||||
const installFromNpmSpecArchiveWithInstallerMock = vi.hoisted(() => vi.fn());
|
||||
const finalizeNpmSpecArchiveInstallMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("./npm-registry-spec.js", () => ({
|
||||
validateRegistryNpmSpec: (...args: unknown[]) => validateRegistryNpmSpecMock(...args),
|
||||
}));
|
||||
|
||||
vi.mock("./npm-pack-install.js", () => ({
|
||||
installFromNpmSpecArchiveWithInstaller: (...args: unknown[]) =>
|
||||
installFromNpmSpecArchiveWithInstallerMock(...args),
|
||||
finalizeNpmSpecArchiveInstall: (...args: unknown[]) => finalizeNpmSpecArchiveInstallMock(...args),
|
||||
}));
|
||||
|
||||
import { installFromValidatedNpmSpecArchive } from "./install-from-npm-spec.js";
|
||||
|
||||
describe("installFromValidatedNpmSpecArchive", () => {
|
||||
it("trims the spec and returns validation errors before running the installer", async () => {
|
||||
validateRegistryNpmSpecMock.mockReturnValueOnce("unsupported npm spec");
|
||||
|
||||
await expect(
|
||||
installFromValidatedNpmSpecArchive({
|
||||
spec: " nope ",
|
||||
timeoutMs: 30_000,
|
||||
tempDirPrefix: "openclaw-npm-",
|
||||
installFromArchive: vi.fn(),
|
||||
archiveInstallParams: {},
|
||||
}),
|
||||
).resolves.toEqual({ ok: false, error: "unsupported npm spec" });
|
||||
|
||||
expect(validateRegistryNpmSpecMock).toHaveBeenCalledWith("nope");
|
||||
expect(installFromNpmSpecArchiveWithInstallerMock).not.toHaveBeenCalled();
|
||||
expect(finalizeNpmSpecArchiveInstallMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("passes the trimmed spec through the archive installer and finalizer", async () => {
|
||||
const installFromArchive = vi.fn();
|
||||
const warn = vi.fn();
|
||||
const onIntegrityDrift = vi.fn();
|
||||
const flowResult = {
|
||||
ok: true,
|
||||
installResult: { ok: true },
|
||||
npmResolution: { version: "1.2.3" },
|
||||
};
|
||||
const finalized = { ok: true, archivePath: "/tmp/pkg.tgz" };
|
||||
validateRegistryNpmSpecMock.mockReturnValueOnce(null);
|
||||
installFromNpmSpecArchiveWithInstallerMock.mockResolvedValueOnce(flowResult);
|
||||
finalizeNpmSpecArchiveInstallMock.mockReturnValueOnce(finalized);
|
||||
|
||||
await expect(
|
||||
installFromValidatedNpmSpecArchive({
|
||||
spec: " @openclaw/demo@beta ",
|
||||
timeoutMs: 45_000,
|
||||
tempDirPrefix: "openclaw-npm-",
|
||||
expectedIntegrity: "sha512-demo",
|
||||
onIntegrityDrift,
|
||||
warn,
|
||||
installFromArchive,
|
||||
archiveInstallParams: { destination: "/tmp/demo" },
|
||||
}),
|
||||
).resolves.toBe(finalized);
|
||||
|
||||
expect(installFromNpmSpecArchiveWithInstallerMock).toHaveBeenCalledWith({
|
||||
tempDirPrefix: "openclaw-npm-",
|
||||
spec: "@openclaw/demo@beta",
|
||||
timeoutMs: 45_000,
|
||||
expectedIntegrity: "sha512-demo",
|
||||
onIntegrityDrift,
|
||||
warn,
|
||||
installFromArchive,
|
||||
archiveInstallParams: { destination: "/tmp/demo" },
|
||||
});
|
||||
expect(finalizeNpmSpecArchiveInstallMock).toHaveBeenCalledWith(flowResult);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { withTempDir } from "../test-helpers/temp-dir.js";
|
||||
|
||||
const fileExistsMock = vi.hoisted(() => vi.fn());
|
||||
const resolveSafeInstallDirMock = vi.hoisted(() => vi.fn());
|
||||
const assertCanonicalPathWithinBaseMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("./archive.js", () => ({
|
||||
fileExists: (...args: unknown[]) => fileExistsMock(...args),
|
||||
}));
|
||||
|
||||
vi.mock("./install-safe-path.js", () => ({
|
||||
resolveSafeInstallDir: (...args: unknown[]) => resolveSafeInstallDirMock(...args),
|
||||
assertCanonicalPathWithinBase: (...args: unknown[]) => assertCanonicalPathWithinBaseMock(...args),
|
||||
}));
|
||||
|
||||
import { ensureInstallTargetAvailable, resolveCanonicalInstallTarget } from "./install-target.js";
|
||||
|
||||
beforeEach(() => {
|
||||
fileExistsMock.mockReset();
|
||||
resolveSafeInstallDirMock.mockReset();
|
||||
assertCanonicalPathWithinBaseMock.mockReset();
|
||||
});
|
||||
|
||||
describe("resolveCanonicalInstallTarget", () => {
|
||||
it("creates the base dir and returns early for invalid install ids", async () => {
|
||||
await withTempDir({ prefix: "openclaw-install-target-" }, async (root) => {
|
||||
const baseDir = path.join(root, "plugins");
|
||||
resolveSafeInstallDirMock.mockReturnValueOnce({
|
||||
ok: false,
|
||||
error: "bad id",
|
||||
});
|
||||
|
||||
await expect(
|
||||
resolveCanonicalInstallTarget({
|
||||
baseDir,
|
||||
id: "../oops",
|
||||
invalidNameMessage: "bad id",
|
||||
boundaryLabel: "plugin dir",
|
||||
}),
|
||||
).resolves.toEqual({ ok: false, error: "bad id" });
|
||||
|
||||
await expect(fs.stat(baseDir)).resolves.toMatchObject({ isDirectory: expect.any(Function) });
|
||||
expect(assertCanonicalPathWithinBaseMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("returns canonical boundary errors for Error and non-Error throws", async () => {
|
||||
await withTempDir({ prefix: "openclaw-install-target-" }, async (baseDir) => {
|
||||
const targetDir = path.join(baseDir, "demo");
|
||||
resolveSafeInstallDirMock.mockReturnValue({
|
||||
ok: true,
|
||||
path: targetDir,
|
||||
});
|
||||
assertCanonicalPathWithinBaseMock.mockRejectedValueOnce(new Error("escaped"));
|
||||
assertCanonicalPathWithinBaseMock.mockRejectedValueOnce("boom");
|
||||
|
||||
await expect(
|
||||
resolveCanonicalInstallTarget({
|
||||
baseDir,
|
||||
id: "demo",
|
||||
invalidNameMessage: "bad id",
|
||||
boundaryLabel: "plugin dir",
|
||||
}),
|
||||
).resolves.toEqual({ ok: false, error: "escaped" });
|
||||
|
||||
await expect(
|
||||
resolveCanonicalInstallTarget({
|
||||
baseDir,
|
||||
id: "demo",
|
||||
invalidNameMessage: "bad id",
|
||||
boundaryLabel: "plugin dir",
|
||||
}),
|
||||
).resolves.toEqual({ ok: false, error: "boom" });
|
||||
});
|
||||
});
|
||||
|
||||
it("returns the resolved target path on success", async () => {
|
||||
await withTempDir({ prefix: "openclaw-install-target-" }, async (baseDir) => {
|
||||
const targetDir = path.join(baseDir, "demo");
|
||||
resolveSafeInstallDirMock.mockReturnValueOnce({
|
||||
ok: true,
|
||||
path: targetDir,
|
||||
});
|
||||
|
||||
await expect(
|
||||
resolveCanonicalInstallTarget({
|
||||
baseDir,
|
||||
id: "demo",
|
||||
invalidNameMessage: "bad id",
|
||||
boundaryLabel: "plugin dir",
|
||||
}),
|
||||
).resolves.toEqual({ ok: true, targetDir });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("ensureInstallTargetAvailable", () => {
|
||||
it("blocks only install mode when the target already exists", async () => {
|
||||
fileExistsMock.mockResolvedValueOnce(true);
|
||||
fileExistsMock.mockResolvedValueOnce(false);
|
||||
|
||||
await expect(
|
||||
ensureInstallTargetAvailable({
|
||||
mode: "install",
|
||||
targetDir: "/tmp/demo",
|
||||
alreadyExistsError: "already there",
|
||||
}),
|
||||
).resolves.toEqual({ ok: false, error: "already there" });
|
||||
|
||||
await expect(
|
||||
ensureInstallTargetAvailable({
|
||||
mode: "update",
|
||||
targetDir: "/tmp/demo",
|
||||
alreadyExistsError: "already there",
|
||||
}),
|
||||
).resolves.toEqual({ ok: true });
|
||||
|
||||
await expect(
|
||||
ensureInstallTargetAvailable({
|
||||
mode: "install",
|
||||
targetDir: "/tmp/demo",
|
||||
alreadyExistsError: "already there",
|
||||
}),
|
||||
).resolves.toEqual({ ok: true });
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { withTempDir } from "../test-helpers/temp-dir.js";
|
||||
import { loadJsonFile, saveJsonFile } from "./json-file.js";
|
||||
|
||||
describe("json-file helpers", () => {
|
||||
it("returns undefined for missing and invalid JSON files", async () => {
|
||||
await withTempDir({ prefix: "openclaw-json-file-" }, async (root) => {
|
||||
const pathname = path.join(root, "config.json");
|
||||
expect(loadJsonFile(pathname)).toBeUndefined();
|
||||
|
||||
fs.writeFileSync(pathname, "{", "utf8");
|
||||
expect(loadJsonFile(pathname)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("creates parent dirs, writes a trailing newline, and loads the saved object", async () => {
|
||||
await withTempDir({ prefix: "openclaw-json-file-" }, async (root) => {
|
||||
const pathname = path.join(root, "nested", "config.json");
|
||||
saveJsonFile(pathname, { enabled: true, count: 2 });
|
||||
|
||||
const raw = fs.readFileSync(pathname, "utf8");
|
||||
expect(raw.endsWith("\n")).toBe(true);
|
||||
expect(loadJsonFile(pathname)).toEqual({ enabled: true, count: 2 });
|
||||
|
||||
const fileMode = fs.statSync(pathname).mode & 0o777;
|
||||
const dirMode = fs.statSync(path.dirname(pathname)).mode & 0o777;
|
||||
expect(fileMode).toBe(0o600);
|
||||
expect(dirMode).toBe(0o700);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
import { rejectPendingPairingRequest } from "./pairing-pending.js";
|
||||
|
||||
describe("rejectPendingPairingRequest", () => {
|
||||
it("returns null and skips persistence when the request is missing", async () => {
|
||||
const persistState = vi.fn();
|
||||
|
||||
await expect(
|
||||
rejectPendingPairingRequest({
|
||||
requestId: "missing",
|
||||
idKey: "deviceId",
|
||||
loadState: async () => ({ pendingById: {} }),
|
||||
persistState,
|
||||
getId: (pending: { id: string }) => pending.id,
|
||||
}),
|
||||
).resolves.toBeNull();
|
||||
|
||||
expect(persistState).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("removes the request, persists, and returns the dynamic id key", async () => {
|
||||
const state = {
|
||||
pendingById: {
|
||||
keep: { accountId: "keep-me" },
|
||||
reject: { accountId: "acct-42" },
|
||||
},
|
||||
};
|
||||
const persistState = vi.fn(async () => undefined);
|
||||
|
||||
await expect(
|
||||
rejectPendingPairingRequest({
|
||||
requestId: "reject",
|
||||
idKey: "accountId",
|
||||
loadState: async () => state,
|
||||
persistState,
|
||||
getId: (pending) => pending.accountId,
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
requestId: "reject",
|
||||
accountId: "acct-42",
|
||||
});
|
||||
|
||||
expect(state.pendingById).toEqual({
|
||||
keep: { accountId: "keep-me" },
|
||||
});
|
||||
expect(persistState).toHaveBeenCalledWith(state);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue