mirror of
https://github.com/morluto/rea.git
synced 2026-11-14 10:43:30 +00:00
* fix(desktop): retain capture identity and cleanup ownership * fix(desktop): validate destinations and finish inputs before export * fix(desktop): preserve package ownership and apt trust boundaries * fix(sqlite): preserve observed facts and distinguish special real values * fix(desktop): bind computer files to data-object metadata * test(desktop): count the tagged negative-zero cell spelling SQLite view cells encode negative zero as a real object so it stays distinct from the text "-0". The byte budget follows that spelling. * fix(desktop): keep unresolved discovered state directories Canonicalizing an unreadable path or dangling symlink aborted capture before the inventory could record that the path was unknown. Those discovered directories now keep their absolute spelling in the scope. * test(desktop): keep the capture scope suite within its line limit The unresolved state-directory case lives beside the suite so the existing callback stays within the lint limit. * fix(desktop): retain cancellation through capture input cleanup * fix(desktop): avoid reserved Pydantic settings properties --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
119 lines
4.1 KiB
TypeScript
119 lines
4.1 KiB
TypeScript
import {
|
|
mkdir,
|
|
open,
|
|
readFile,
|
|
readdir,
|
|
rm,
|
|
writeFile,
|
|
type FileHandle,
|
|
} from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
|
|
import { createTestTempDirectory } from "../../../tests/fixtures/temporaryDirectory.js";
|
|
import type { Evidence } from "../../domain/evidence.js";
|
|
import { OwnedFileHandle } from "../../filesystem/OwnedFileHandle.js";
|
|
import { CaptureFile, prepareOutputDirectory } from "./DesktopCaptureOutput.js";
|
|
|
|
const evidence = { id: "evidence" } as unknown as Evidence;
|
|
|
|
it.each(["", "/child"])(
|
|
"refuses an output path blocked by a regular file (%s)",
|
|
async (suffix) => {
|
|
const root = await createTestTempDirectory("desktop-output-file-");
|
|
const file = join(root, "existing");
|
|
await writeFile(file, "keep");
|
|
await expect(prepareOutputDirectory(`${file}${suffix}`)).resolves.toEqual({
|
|
refusal: "not_a_directory",
|
|
});
|
|
expect(await readFile(file, "utf8")).toBe("keep");
|
|
},
|
|
);
|
|
|
|
describe("CaptureFile", () => {
|
|
it("publishes through its partial file and leaves nothing to discard", async () => {
|
|
const output = await createTestTempDirectory("desktop-output-");
|
|
const captureFile = new CaptureFile(output);
|
|
|
|
const published = await captureFile.publish(evidence);
|
|
await captureFile.discard();
|
|
|
|
expect(published).toBe(join(output, "capture.json"));
|
|
expect(JSON.parse(await readFile(published, "utf8"))).toEqual(evidence);
|
|
expect(await readdir(output)).toEqual(["capture.json"]);
|
|
});
|
|
|
|
it("never removes a partial file another process created", async () => {
|
|
const output = await createTestTempDirectory("desktop-output-foreign-");
|
|
const captureFile = new CaptureFile(output);
|
|
await writeFile(captureFile.partial, "theirs");
|
|
|
|
await captureFile.discard();
|
|
await expect(captureFile.publish(evidence)).rejects.toMatchObject({
|
|
code: "EEXIST",
|
|
});
|
|
await captureFile.discard();
|
|
|
|
expect(await readFile(captureFile.partial, "utf8")).toBe("theirs");
|
|
});
|
|
|
|
it("removes the partial file it created when publication fails", async () => {
|
|
const output = await createTestTempDirectory("desktop-output-rename-");
|
|
const captureFile = new CaptureFile(output);
|
|
await mkdir(join(output, "capture.json", "occupied"), { recursive: true });
|
|
|
|
await expect(captureFile.publish(evidence)).rejects.toThrow();
|
|
expect((await readdir(output)).sort()).toEqual([
|
|
"capture.json",
|
|
"capture.json.partial",
|
|
]);
|
|
await captureFile.discard();
|
|
|
|
expect(await readdir(output)).toEqual(["capture.json"]);
|
|
});
|
|
});
|
|
|
|
/** The prototype every `open` handle shares, so a test can fail its writes. */
|
|
const fileHandlePrototype = async (directory: string): Promise<FileHandle> => {
|
|
const probe = await open(join(directory, "probe"), "w");
|
|
await probe.close();
|
|
await rm(join(directory, "probe"));
|
|
return Object.getPrototypeOf(probe) as FileHandle;
|
|
};
|
|
|
|
describe("CaptureFile handle cleanup", () => {
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it.each([
|
|
{ writeFails: true, reported: "disk full" },
|
|
{ writeFails: false, reported: "close failed" },
|
|
])(
|
|
"keeps the partial file's handle owned when its close fails (write fails: $writeFails)",
|
|
async ({ writeFails, reported }) => {
|
|
const output = await createTestTempDirectory("desktop-output-close-");
|
|
const captureFile = new CaptureFile(output);
|
|
if (writeFails)
|
|
vi.spyOn(
|
|
await fileHandlePrototype(output),
|
|
"writeFile",
|
|
).mockRejectedValueOnce(new Error("disk full"));
|
|
const close = vi
|
|
.spyOn(OwnedFileHandle.prototype, "close")
|
|
.mockRejectedValueOnce(new Error("close failed"));
|
|
|
|
await expect(captureFile.publish(evidence)).rejects.toThrow(reported);
|
|
const [owned] = close.mock.contexts as OwnedFileHandle[];
|
|
expect(owned?.handle.fd).toBeGreaterThanOrEqual(0);
|
|
await captureFile.close();
|
|
|
|
expect(owned?.handle.fd).toBe(-1);
|
|
expect(await readdir(output)).toEqual(["capture.json.partial"]);
|
|
await captureFile.discard();
|
|
expect(await readdir(output)).toEqual([]);
|
|
},
|
|
);
|
|
});
|