|  | 
| 1 |  | -import { describe, test, expect, vi, beforeEach } from "vitest"; | 
| 2 |  | -import * as fs from "node:fs/promises"; | 
|  | 1 | +/* eslint-disable @typescript-eslint/consistent-type-imports */ | 
|  | 2 | +/* eslint-disable @typescript-eslint/no-explicit-any */ | 
|  | 3 | +import { describe, expect, it, vi } from "vitest"; | 
| 3 | 4 | import { createSymlink } from "./symlink"; | 
| 4 |  | -import * as path from "node:path"; | 
| 5 |  | -import type { FileHandle } from "node:fs/promises"; | 
| 6 |  | - | 
| 7 |  | -vi.mock("node:fs/promises", () => ({ | 
| 8 |  | -	rm: vi.fn(), | 
| 9 |  | -	symlink: vi.fn(), | 
| 10 |  | -	rename: vi.fn(), | 
| 11 |  | -	copyFile: vi.fn(), | 
| 12 |  | -	mkdir: vi.fn(), | 
| 13 |  | -	mkdtemp: vi.fn(), | 
| 14 |  | -	open: vi.fn(), | 
| 15 |  | -})); | 
|  | 5 | +import { readFileSync, writeFileSync } from "node:fs"; | 
|  | 6 | +import { lstat, rm } from "node:fs/promises"; | 
|  | 7 | +import { tmpdir } from "node:os"; | 
|  | 8 | +import { join } from "node:path"; | 
|  | 9 | + | 
|  | 10 | +let failSymlink = false; | 
|  | 11 | +vi.mock("node:fs/promises", async (importOriginal) => ({ | 
|  | 12 | +	...(await importOriginal<typeof import("node:fs/promises")>()), | 
|  | 13 | +	symlink: async (...args: any[]) => { | 
|  | 14 | +		if (failSymlink) { | 
|  | 15 | +			failSymlink = false; | 
|  | 16 | +			throw new Error("Symlink not supported"); | 
|  | 17 | +		} | 
| 16 | 18 | 
 | 
| 17 |  | -vi.mock("node:os", () => ({ | 
| 18 |  | -	platform: vi.fn(), | 
|  | 19 | +		// @ts-expect-error - ignore | 
|  | 20 | +		return (await importOriginal<typeof import("node:fs/promises")>()).symlink(...args); | 
|  | 21 | +	}, | 
| 19 | 22 | })); | 
| 20 | 23 | 
 | 
| 21 |  | -describe("createSymlink", () => { | 
| 22 |  | -	const src = "/path/to/src"; | 
| 23 |  | -	const dst = "/path/to/dst"; | 
|  | 24 | +describe("utils/symlink", () => { | 
|  | 25 | +	it("should create a symlink", async () => { | 
|  | 26 | +		writeFileSync(join(tmpdir(), "test.txt"), "hello world"); | 
|  | 27 | +		await createSymlink({ | 
|  | 28 | +			sourcePath: join(tmpdir(), "test.txt"), | 
|  | 29 | +			finalPath: join(tmpdir(), "test-symlink.txt"), | 
|  | 30 | +		}); | 
| 24 | 31 | 
 | 
| 25 |  | -	beforeEach(() => { | 
| 26 |  | -		vi.resetAllMocks(); | 
| 27 |  | -		vi.mocked(fs.mkdtemp).mockImplementation(async (prefix) => `${prefix}/temp`); | 
| 28 |  | -		vi.mocked(fs.open).mockResolvedValue({ close: vi.fn() } as unknown as FileHandle); | 
| 29 |  | -	}); | 
|  | 32 | +		const stats = await lstat(join(tmpdir(), "test-symlink.txt")); | 
|  | 33 | +		expect(stats.isSymbolicLink()).toBe(process.platform !== "win32"); | 
| 30 | 34 | 
 | 
| 31 |  | -	test("should remove existing destination", async () => { | 
| 32 |  | -		await createSymlink(dst, src); | 
| 33 |  | -		expect(fs.rm).toHaveBeenCalledWith(dst); | 
|  | 35 | +		// Test file content | 
|  | 36 | +		const content = readFileSync(join(tmpdir(), "test-symlink.txt"), "utf8"); | 
|  | 37 | +		expect(content).toBe("hello world"); | 
|  | 38 | + | 
|  | 39 | +		// Cleanup | 
|  | 40 | +		await rm(join(tmpdir(), "test-symlink.txt")); | 
|  | 41 | +		await rm(join(tmpdir(), "test.txt")); | 
| 34 | 42 | 	}); | 
| 35 | 43 | 
 | 
| 36 |  | -	describe("symlink not supported (Windows)", () => { | 
| 37 |  | -		beforeEach(() => { | 
| 38 |  | -			vi.mocked(fs.symlink).mockRejectedValue(new Error("Symlink not supported")); | 
|  | 44 | +	it("should work when symlinking twice", async () => { | 
|  | 45 | +		writeFileSync(join(tmpdir(), "test.txt"), "hello world"); | 
|  | 46 | +		writeFileSync(join(tmpdir(), "test2.txt"), "hello world2"); | 
|  | 47 | +		await createSymlink({ | 
|  | 48 | +			sourcePath: join(tmpdir(), "test.txt"), | 
|  | 49 | +			finalPath: join(tmpdir(), "test-symlink.txt"), | 
| 39 | 50 | 		}); | 
| 40 |  | - | 
| 41 |  | -		test("should copyfile", async () => { | 
| 42 |  | -			await createSymlink(dst, src); | 
| 43 |  | -			expect(fs.copyFile).toHaveBeenCalledWith(path.resolve(dst), path.resolve(src)); | 
|  | 51 | +		await createSymlink({ | 
|  | 52 | +			sourcePath: join(tmpdir(), "test2.txt"), | 
|  | 53 | +			finalPath: join(tmpdir(), "test-symlink.txt"), | 
| 44 | 54 | 		}); | 
| 45 | 55 | 
 | 
| 46 |  | -		test("should rename file if new_blob is true", async () => { | 
| 47 |  | -			await createSymlink(dst, src, true); | 
| 48 |  | -			expect(fs.rename).toHaveBeenCalledWith(path.resolve(dst), path.resolve(src)); | 
| 49 |  | -		}); | 
|  | 56 | +		const stats = await lstat(join(tmpdir(), "test-symlink.txt")); | 
|  | 57 | +		expect(stats.isSymbolicLink()).toBe(process.platform !== "win32"); | 
|  | 58 | + | 
|  | 59 | +		// Test file content | 
|  | 60 | +		const content = readFileSync(join(tmpdir(), "test-symlink.txt"), "utf8"); | 
|  | 61 | +		expect(content).toBe("hello world2"); | 
|  | 62 | + | 
|  | 63 | +		// Cleanup | 
|  | 64 | +		await rm(join(tmpdir(), "test-symlink.txt")); | 
|  | 65 | +		await rm(join(tmpdir(), "test.txt")); | 
|  | 66 | +		await rm(join(tmpdir(), "test2.txt")); | 
| 50 | 67 | 	}); | 
| 51 | 68 | 
 | 
| 52 |  | -	describe("symlink supported", () => { | 
| 53 |  | -		test("should symlink", async () => { | 
| 54 |  | -			await createSymlink(dst, src); | 
| 55 |  | -			expect(fs.symlink).toHaveBeenCalledWith(path.resolve(dst), path.resolve(src)); | 
| 56 |  | -		}); | 
|  | 69 | +	it("should work when symlink doesn't work (windows)", async () => { | 
|  | 70 | +		writeFileSync(join(tmpdir(), "test.txt"), "hello world"); | 
| 57 | 71 | 
 | 
| 58 |  | -		test("should symlink if new_blob is true", async () => { | 
| 59 |  | -			await createSymlink(dst, src, true); | 
| 60 |  | -			expect(fs.symlink).toHaveBeenCalledWith(path.resolve(dst), path.resolve(src)); | 
|  | 72 | +		failSymlink = true; | 
|  | 73 | +		await createSymlink({ | 
|  | 74 | +			sourcePath: join(tmpdir(), "test.txt"), | 
|  | 75 | +			finalPath: join(tmpdir(), "test-symlink.txt"), | 
| 61 | 76 | 		}); | 
|  | 77 | + | 
|  | 78 | +		const stats = await lstat(join(tmpdir(), "test-symlink.txt")); | 
|  | 79 | +		expect(stats.isSymbolicLink()).toBe(false); | 
|  | 80 | + | 
|  | 81 | +		// Test file content | 
|  | 82 | +		const content = readFileSync(join(tmpdir(), "test-symlink.txt"), "utf8"); | 
|  | 83 | +		expect(content).toBe("hello world"); | 
|  | 84 | + | 
|  | 85 | +		// Cleanup | 
|  | 86 | +		await rm(join(tmpdir(), "test-symlink.txt")); | 
|  | 87 | +		await rm(join(tmpdir(), "test.txt")); | 
| 62 | 88 | 	}); | 
| 63 | 89 | }); | 
0 commit comments