Files
2026-07-11 17:08:37 +00:00

238 lines
7.1 KiB
JavaScript

import test, { describe, beforeEach, afterEach, mock } from "node:test"
import assert from "node:assert"
import fs from "fs"
import path from "path"
import os from "os"
import { symlinkOrCopySync, symlinkOrCopy } from "./helpers.js"
function makeTmpDir() {
return fs.mkdtempSync(path.join(os.tmpdir(), "quartz-symlink-test-"))
}
function makeTarget(tmpDir) {
const target = path.join(tmpDir, "target")
fs.mkdirSync(target)
fs.writeFileSync(path.join(target, "marker.txt"), "hello")
return target
}
describe("symlinkOrCopySync", () => {
let tmpDir
beforeEach(() => {
tmpDir = makeTmpDir()
})
afterEach(() => {
fs.rmSync(tmpDir, { recursive: true, force: true })
})
test("creates a symlink on success", () => {
const target = makeTarget(tmpDir)
const linkPath = path.join(tmpDir, "link")
symlinkOrCopySync(target, linkPath)
const stat = fs.lstatSync(linkPath)
assert.ok(stat.isSymbolicLink())
assert.ok(fs.existsSync(path.join(linkPath, "marker.txt")))
})
test("silently succeeds when link already exists (EEXIST)", () => {
const target = makeTarget(tmpDir)
const linkPath = path.join(tmpDir, "link")
symlinkOrCopySync(target, linkPath)
assert.doesNotThrow(() => symlinkOrCopySync(target, linkPath))
})
test("re-throws non-EPERM errors", () => {
const target = makeTarget(tmpDir)
const linkPath = path.join(tmpDir, "nonexistent-parent", "link")
assert.throws(
() => symlinkOrCopySync(target, linkPath),
(err) => {
return err.code === "ENOENT"
},
)
})
test("falls back to junction on Windows EPERM", () => {
const target = makeTarget(tmpDir)
const linkPath = path.join(tmpDir, "link")
const originalPlatform = Object.getOwnPropertyDescriptor(process, "platform")
Object.defineProperty(process, "platform", { value: "win32", configurable: true })
let callCount = 0
const originalSymlinkSync = fs.symlinkSync
mock.method(fs, "symlinkSync", (t, lp, type) => {
callCount++
if (callCount === 1 && type === "dir") {
const err = new Error("EPERM: operation not permitted, symlink")
err.code = "EPERM"
err.errno = -4048
err.syscall = "symlink"
throw err
}
return originalSymlinkSync(t, lp, type)
})
try {
symlinkOrCopySync(target, linkPath)
assert.ok(fs.existsSync(path.join(linkPath, "marker.txt")))
assert.strictEqual(callCount, 2)
} finally {
fs.symlinkSync.mock.restore()
if (originalPlatform) {
Object.defineProperty(process, "platform", originalPlatform)
}
}
})
test("falls back to copy when both symlink and junction fail on Windows", () => {
const target = makeTarget(tmpDir)
const linkPath = path.join(tmpDir, "link")
const originalPlatform = Object.getOwnPropertyDescriptor(process, "platform")
Object.defineProperty(process, "platform", { value: "win32", configurable: true })
const originalSymlinkSync = fs.symlinkSync
mock.method(fs, "symlinkSync", (_t, _lp, _type) => {
const err = new Error("EPERM: operation not permitted, symlink")
err.code = "EPERM"
err.errno = -4048
err.syscall = "symlink"
throw err
})
try {
symlinkOrCopySync(target, linkPath)
const stat = fs.lstatSync(linkPath)
assert.ok(stat.isDirectory(), "fallback should produce a real directory, not a symlink")
assert.strictEqual(fs.readFileSync(path.join(linkPath, "marker.txt"), "utf-8"), "hello")
} finally {
fs.symlinkSync.mock.restore()
if (originalPlatform) {
Object.defineProperty(process, "platform", originalPlatform)
}
}
})
test("does not fall back on EPERM when not on Windows", () => {
const target = makeTarget(tmpDir)
const linkPath = path.join(tmpDir, "link")
const originalPlatform = Object.getOwnPropertyDescriptor(process, "platform")
Object.defineProperty(process, "platform", { value: "linux", configurable: true })
const originalSymlinkSync = fs.symlinkSync
mock.method(fs, "symlinkSync", (_t, _lp, _type) => {
const err = new Error("EPERM: operation not permitted, symlink")
err.code = "EPERM"
throw err
})
try {
assert.throws(
() => symlinkOrCopySync(target, linkPath),
(err) => err.code === "EPERM",
)
} finally {
fs.symlinkSync.mock.restore()
if (originalPlatform) {
Object.defineProperty(process, "platform", originalPlatform)
}
}
})
})
describe("symlinkOrCopy", () => {
let tmpDir
beforeEach(() => {
tmpDir = makeTmpDir()
})
afterEach(() => {
fs.rmSync(tmpDir, { recursive: true, force: true })
})
test("creates a symlink on success", async () => {
const target = makeTarget(tmpDir)
const linkPath = path.join(tmpDir, "link")
await symlinkOrCopy(target, linkPath)
const stat = fs.lstatSync(linkPath)
assert.ok(stat.isSymbolicLink())
assert.ok(fs.existsSync(path.join(linkPath, "marker.txt")))
})
test("silently succeeds when link already exists (EEXIST)", async () => {
const target = makeTarget(tmpDir)
const linkPath = path.join(tmpDir, "link")
await symlinkOrCopy(target, linkPath)
await assert.doesNotReject(() => symlinkOrCopy(target, linkPath))
})
test("falls back to copy when both symlink and junction fail on Windows", async () => {
const target = makeTarget(tmpDir)
const linkPath = path.join(tmpDir, "link")
const originalPlatform = Object.getOwnPropertyDescriptor(process, "platform")
Object.defineProperty(process, "platform", { value: "win32", configurable: true })
const originalSymlink = fs.promises.symlink
mock.method(fs.promises, "symlink", async (_t, _lp, _type) => {
const err = new Error("EPERM: operation not permitted, symlink")
err.code = "EPERM"
err.errno = -4048
err.syscall = "symlink"
throw err
})
try {
await symlinkOrCopy(target, linkPath)
const stat = fs.lstatSync(linkPath)
assert.ok(stat.isDirectory(), "fallback should produce a real directory, not a symlink")
assert.strictEqual(fs.readFileSync(path.join(linkPath, "marker.txt"), "utf-8"), "hello")
} finally {
fs.promises.symlink.mock.restore()
if (originalPlatform) {
Object.defineProperty(process, "platform", originalPlatform)
}
}
})
test("does not fall back on EPERM when not on Windows", async () => {
const target = makeTarget(tmpDir)
const linkPath = path.join(tmpDir, "link")
const originalPlatform = Object.getOwnPropertyDescriptor(process, "platform")
Object.defineProperty(process, "platform", { value: "linux", configurable: true })
mock.method(fs.promises, "symlink", async (_t, _lp, _type) => {
const err = new Error("EPERM: operation not permitted, symlink")
err.code = "EPERM"
throw err
})
try {
await assert.rejects(
() => symlinkOrCopy(target, linkPath),
(err) => err.code === "EPERM",
)
} finally {
fs.promises.symlink.mock.restore()
if (originalPlatform) {
Object.defineProperty(process, "platform", originalPlatform)
}
}
})
})