Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]
import fs from "node:fs/promises";
import path from "node:path";
import { setTimeout as sleep } from "node:timers/promises";
import { afterEach, describe, expect, it, vi } from "vitest";
import { withTempDir } from "../test-helpers/temp-dir.js";
import { createAsyncLock, readJsonFile, writeJsonAtomic, writeTextAtomic } from "./json-files.js";
const originalPlatformDescriptor = Object.getOwnPropertyDescriptor(process, "platform");
afterEach(() => {
vi.restoreAllMocks();
if (originalPlatformDescriptor) {
Object.defineProperty(process, "platform", originalPlatformDescriptor);
}
});
describe("json file helpers", () => {
it.each([
{
name: "reads valid json",
setup: async (base: string) => {
const filePath = path.join(base, "valid.json");
await fs.writeFile(filePath, '{"ok":true}', "utf8");
return filePath;
},
expected: { ok: true },
},
{
name: "returns null for invalid files",
setup: async (base: string) => {
const filePath = path.join(base, "invalid.json");
await fs.writeFile(filePath, "{not-json}", "utf8");
return filePath;
},
expected: null,
},
{
name: "returns null for missing files",
setup: async (base: string) => path.join(base, "missing.json"),
expected: null,
},
])("$name", async ({ setup, expected }) => {
await withTempDir({ prefix: "openclaw-json-files-" }, async (base) => {
await expect(readJsonFile(await setup(base))).resolves.toEqual(expected);
});
});
it("writes json atomically with pretty formatting and optional trailing newline", async () => {
await withTempDir({ prefix: "openclaw-json-files-" }, async (base) => {
const filePath = path.join(base, "nested", "config.json");
await writeJsonAtomic(
filePath,
{ ok: true, nested: { value: 1 } },
{ trailingNewline: true, ensureDirMode: 0o755 },
);
await expect(fs.readFile(filePath, "utf8")).resolves.toBe(
'{\n "ok": true,\n "nested": {\n "value": 1\n }\n}\n',
);
});
});
it.each([
{ input: "hello", expected: "hello\n" },
{ input: "hello\n", expected: "hello\n" },
])("writes text atomically for %j", async ({ input, expected }) => {
await withTempDir({ prefix: "openclaw-json-files-" }, async (base) => {
const filePath = path.join(base, "nested", "note.txt");
await writeTextAtomic(filePath, input, { appendTrailingNewline: true });
await expect(fs.readFile(filePath, "utf8")).resolves.toBe(expected);
});
});
it("falls back to copy-on-replace for Windows rename EPERM", async () => {
await withTempDir({ prefix: "openclaw-json-files-" }, async (base) => {
const filePath = path.join(base, "state.json");
await fs.writeFile(filePath, "old", "utf8");
Object.defineProperty(process, "platform", { value: "win32", configurable: true });
const renameError = Object.assign(new Error("EPERM"), { code: "EPERM" });
const renameSpy = vi.spyOn(fs, "rename").mockRejectedValueOnce(renameError);
const copySpy = vi.spyOn(fs, "copyFile");
await writeTextAtomic(filePath, "new");
expect(renameSpy).toHaveBeenCalledOnce();
expect(copySpy).toHaveBeenCalledOnce();
await expect(fs.readFile(filePath, "utf8")).resolves.toBe("new");
});
});
it.each([
{
name: "serializes async lock callers even across rejections",
firstTask: async (events: string[]) => {
events.push("first:start");
await sleep(20);
events.push("first:end");
throw new Error("boom");
},
expectedFirstError: "boom",
expectedEvents: ["first:start", "first:end", "second:start", "second:end"],
},
{
name: "releases the async lock after synchronous throws",
firstTask: async (events: string[]) => {
events.push("first:start");
throw new Error("sync boom");
},
expectedFirstError: "sync boom",
expectedEvents: ["first:start", "second:start", "second:end"],
},
])("$name", async ({ firstTask, expectedFirstError, expectedEvents }) => {
const withLock = createAsyncLock();
const events: string[] = [];
const first = withLock(() => firstTask(events));
const second = withLock(async () => {
events.push("second:start");
events.push("second:end");
return "ok";
});
await expect(first).rejects.toThrow(expectedFirstError);
await expect(second).resolves.toBe("ok");
expect(events).toEqual(expectedEvents);
});
});
¤ Dauer der Verarbeitung: 0.21 Sekunden
(vorverarbeitet am 2026-04-27)
¤
*© Formatika GbR, Deutschland
|
|