| import path from "node:path"; |
| import { beforeEach, describe, expect, it, vi } from "vitest"; |
|
|
| const resolveBoundaryPathSyncMock = vi.hoisted(() => vi.fn()); |
| const resolveBoundaryPathMock = vi.hoisted(() => vi.fn()); |
| const openVerifiedFileSyncMock = vi.hoisted(() => vi.fn()); |
|
|
| vi.mock("./boundary-path.js", () => ({ |
| resolveBoundaryPathSync: (...args: unknown[]) => resolveBoundaryPathSyncMock(...args), |
| resolveBoundaryPath: (...args: unknown[]) => resolveBoundaryPathMock(...args), |
| })); |
|
|
| vi.mock("./safe-open-sync.js", () => ({ |
| openVerifiedFileSync: (...args: unknown[]) => openVerifiedFileSyncMock(...args), |
| })); |
|
|
| const { canUseBoundaryFileOpen, openBoundaryFile, openBoundaryFileSync } = |
| await import("./boundary-file-read.js"); |
|
|
| describe("boundary-file-read", () => { |
| beforeEach(() => { |
| resolveBoundaryPathSyncMock.mockReset(); |
| resolveBoundaryPathMock.mockReset(); |
| openVerifiedFileSyncMock.mockReset(); |
| }); |
|
|
| it("recognizes the required sync fs surface", () => { |
| const validFs = { |
| openSync() {}, |
| closeSync() {}, |
| fstatSync() {}, |
| lstatSync() {}, |
| realpathSync() {}, |
| readFileSync() {}, |
| constants: {}, |
| }; |
|
|
| expect(canUseBoundaryFileOpen(validFs as never)).toBe(true); |
| expect( |
| canUseBoundaryFileOpen({ |
| ...validFs, |
| openSync: undefined, |
| } as never), |
| ).toBe(false); |
| expect( |
| canUseBoundaryFileOpen({ |
| ...validFs, |
| constants: null, |
| } as never), |
| ).toBe(false); |
| }); |
|
|
| it("maps sync boundary resolution into verified file opens", () => { |
| const stat = { size: 3 } as never; |
| const ioFs = { marker: "io" } as never; |
| const absolutePath = path.resolve("plugin.json"); |
|
|
| resolveBoundaryPathSyncMock.mockReturnValue({ |
| canonicalPath: "/real/plugin.json", |
| rootCanonicalPath: "/real/root", |
| }); |
| openVerifiedFileSyncMock.mockReturnValue({ |
| ok: true, |
| path: "/real/plugin.json", |
| fd: 7, |
| stat, |
| }); |
|
|
| const opened = openBoundaryFileSync({ |
| absolutePath: "plugin.json", |
| rootPath: "/workspace", |
| boundaryLabel: "plugin root", |
| ioFs, |
| }); |
|
|
| expect(resolveBoundaryPathSyncMock).toHaveBeenCalledWith({ |
| absolutePath, |
| rootPath: "/workspace", |
| rootCanonicalPath: undefined, |
| boundaryLabel: "plugin root", |
| skipLexicalRootCheck: undefined, |
| }); |
| expect(openVerifiedFileSyncMock).toHaveBeenCalledWith({ |
| filePath: absolutePath, |
| resolvedPath: "/real/plugin.json", |
| rejectHardlinks: true, |
| maxBytes: undefined, |
| allowedType: undefined, |
| ioFs, |
| }); |
| expect(opened).toEqual({ |
| ok: true, |
| path: "/real/plugin.json", |
| fd: 7, |
| stat, |
| rootRealPath: "/real/root", |
| }); |
| }); |
|
|
| it("returns validation errors when sync boundary resolution throws", () => { |
| const error = new Error("outside root"); |
| resolveBoundaryPathSyncMock.mockImplementation(() => { |
| throw error; |
| }); |
|
|
| const opened = openBoundaryFileSync({ |
| absolutePath: "plugin.json", |
| rootPath: "/workspace", |
| boundaryLabel: "plugin root", |
| }); |
|
|
| expect(opened).toEqual({ |
| ok: false, |
| reason: "validation", |
| error, |
| }); |
| expect(openVerifiedFileSyncMock).not.toHaveBeenCalled(); |
| }); |
|
|
| it("guards against unexpected async sync-resolution results", () => { |
| resolveBoundaryPathSyncMock.mockReturnValue( |
| Promise.resolve({ |
| canonicalPath: "/real/plugin.json", |
| rootCanonicalPath: "/real/root", |
| }), |
| ); |
|
|
| const opened = openBoundaryFileSync({ |
| absolutePath: "plugin.json", |
| rootPath: "/workspace", |
| boundaryLabel: "plugin root", |
| }); |
|
|
| expect(opened.ok).toBe(false); |
| if (opened.ok) { |
| return; |
| } |
| expect(opened.reason).toBe("validation"); |
| expect(String(opened.error)).toContain("Unexpected async boundary resolution"); |
| }); |
|
|
| it("awaits async boundary resolution before verifying the file", async () => { |
| const ioFs = { marker: "io" } as never; |
| const absolutePath = path.resolve("notes.txt"); |
|
|
| resolveBoundaryPathMock.mockResolvedValue({ |
| canonicalPath: "/real/notes.txt", |
| rootCanonicalPath: "/real/root", |
| }); |
| openVerifiedFileSyncMock.mockReturnValue({ |
| ok: false, |
| reason: "validation", |
| error: new Error("blocked"), |
| }); |
|
|
| const opened = await openBoundaryFile({ |
| absolutePath: "notes.txt", |
| rootPath: "/workspace", |
| boundaryLabel: "workspace", |
| aliasPolicy: { allowFinalSymlinkForUnlink: true }, |
| ioFs, |
| }); |
|
|
| expect(resolveBoundaryPathMock).toHaveBeenCalledWith({ |
| absolutePath, |
| rootPath: "/workspace", |
| rootCanonicalPath: undefined, |
| boundaryLabel: "workspace", |
| policy: { allowFinalSymlinkForUnlink: true }, |
| skipLexicalRootCheck: undefined, |
| }); |
| expect(openVerifiedFileSyncMock).toHaveBeenCalledWith({ |
| filePath: absolutePath, |
| resolvedPath: "/real/notes.txt", |
| rejectHardlinks: true, |
| maxBytes: undefined, |
| allowedType: undefined, |
| ioFs, |
| }); |
| expect(opened).toEqual({ |
| ok: false, |
| reason: "validation", |
| error: expect.any(Error), |
| }); |
| }); |
|
|
| it("maps async boundary resolution failures to validation errors", async () => { |
| const error = new Error("escaped"); |
| resolveBoundaryPathMock.mockRejectedValue(error); |
|
|
| const opened = await openBoundaryFile({ |
| absolutePath: "notes.txt", |
| rootPath: "/workspace", |
| boundaryLabel: "workspace", |
| }); |
|
|
| expect(opened).toEqual({ |
| ok: false, |
| reason: "validation", |
| error, |
| }); |
| expect(openVerifiedFileSyncMock).not.toHaveBeenCalled(); |
| }); |
| }); |
|
|