import { describe, expect, it, beforeEach, afterEach } from "vitest";
import { mkdtemp, rm, writeFile, symlink, chmod, realpath, mkdir } from "node:fs/promises";
import { tmpdir, homedir } from "node:os";
import { join } from "node:path";
import { ErrorCode } from "@pocketcoder/shared/constants";
import { AppError } from "../../errors/index.js";
import { validateProjectPath } from "../validate-project-path.js";
describe("validate-project-path", () => {
    let tempDir;
    let projectDir;
    beforeEach(async () => {
        // Use realpath to get the canonical path (handles macOS /var -> /private/var symlink)
        const rawTempDir = await mkdtemp(join(tmpdir(), "pocketcoder-validate-path-test-"));
        tempDir = await realpath(rawTempDir);
        projectDir = join(tempDir, "my-project");
        await mkdir(projectDir, { recursive: true });
    });
    afterEach(async () => {
        await rm(tempDir, { recursive: true, force: true });
    });
    it("should accept a valid project directory path", async () => {
        const result = await validateProjectPath(projectDir);
        expect(result).toBe(projectDir);
    });
    it("should resolve relative paths to absolute", async () => {
        const result = await validateProjectPath(projectDir);
        expect(result).toBe(projectDir);
        expect(result.startsWith("/")).toBe(true);
    });
    it("should reject non-existent paths", async () => {
        const nonExistent = join(tempDir, "does-not-exist");
        try {
            await validateProjectPath(nonExistent);
            expect.fail("Should have thrown");
        }
        catch (err) {
            expect(err).toBeInstanceOf(AppError);
            expect(err.code).toBe(ErrorCode.PATH_NOT_FOUND);
        }
    });
    it("should reject file paths (not directories)", async () => {
        const filePath = join(tempDir, "some-file.txt");
        await writeFile(filePath, "hello");
        try {
            await validateProjectPath(filePath);
            expect.fail("Should have thrown");
        }
        catch (err) {
            expect(err).toBeInstanceOf(AppError);
            expect(err.code).toBe(ErrorCode.INVALID_REQUEST);
            expect(err.message).toContain("not a directory");
        }
    });
    it("should reject empty path", async () => {
        try {
            await validateProjectPath("");
            expect.fail("Should have thrown");
        }
        catch (err) {
            expect(err).toBeInstanceOf(AppError);
            expect(err.code).toBe(ErrorCode.INVALID_REQUEST);
            expect(err.message).toContain("cannot be empty");
        }
    });
    it("should reject whitespace-only path", async () => {
        try {
            await validateProjectPath("   ");
            expect.fail("Should have thrown");
        }
        catch (err) {
            expect(err).toBeInstanceOf(AppError);
            expect(err.code).toBe(ErrorCode.INVALID_REQUEST);
        }
    });
    it("should reject system paths (/etc)", async () => {
        // Only test on non-Windows
        if (process.platform === "win32")
            return;
        try {
            await validateProjectPath("/etc");
            expect.fail("Should have thrown");
        }
        catch (err) {
            expect(err).toBeInstanceOf(AppError);
            expect(err.code).toBe(ErrorCode.INVALID_REQUEST);
            expect(err.message).toContain("System paths");
        }
    });
    it("should reject system paths (/usr)", async () => {
        if (process.platform === "win32")
            return;
        try {
            await validateProjectPath("/usr");
            expect.fail("Should have thrown");
        }
        catch (err) {
            expect(err).toBeInstanceOf(AppError);
            expect(err.code).toBe(ErrorCode.INVALID_REQUEST);
            expect(err.message).toContain("System paths");
        }
    });
    it("should reject subdirectories of system paths (/usr/local)", async () => {
        if (process.platform === "win32")
            return;
        try {
            await validateProjectPath("/usr/local");
            expect.fail("Should have thrown");
        }
        catch (err) {
            expect(err).toBeInstanceOf(AppError);
            expect(err.code).toBe(ErrorCode.INVALID_REQUEST);
            expect(err.message).toContain("System paths");
        }
    });
    it("should reject the filesystem root", async () => {
        try {
            await validateProjectPath("/");
            expect.fail("Should have thrown");
        }
        catch (err) {
            expect(err).toBeInstanceOf(AppError);
            expect(err.code).toBe(ErrorCode.INVALID_REQUEST);
            expect(err.message).toContain("Root directory");
        }
    });
    it("should reject the home directory itself", async () => {
        try {
            await validateProjectPath(homedir());
            expect.fail("Should have thrown");
        }
        catch (err) {
            expect(err).toBeInstanceOf(AppError);
            expect(err.code).toBe(ErrorCode.INVALID_REQUEST);
            expect(err.message).toContain("Home directory");
        }
    });
    it("should resolve symlinks and return the real path", async () => {
        const linkPath = join(tempDir, "link-to-project");
        await symlink(projectDir, linkPath);
        const result = await validateProjectPath(linkPath);
        expect(result).toBe(projectDir);
    });
    it("should reject symlinks pointing to non-existent targets", async () => {
        const linkPath = join(tempDir, "dangling-link");
        await symlink(join(tempDir, "nonexistent"), linkPath);
        try {
            await validateProjectPath(linkPath);
            expect.fail("Should have thrown");
        }
        catch (err) {
            expect(err).toBeInstanceOf(AppError);
            expect(err.code).toBe(ErrorCode.PATH_NOT_FOUND);
        }
    });
    it("should reject directories without read permission", async () => {
        if (process.platform === "win32")
            return;
        // Skip if running as root (root can read anything)
        if (process.getuid?.() === 0)
            return;
        const restrictedDir = join(tempDir, "restricted");
        await mkdir(restrictedDir);
        await chmod(restrictedDir, 0o000);
        try {
            await validateProjectPath(restrictedDir);
            expect.fail("Should have thrown");
        }
        catch (err) {
            expect(err).toBeInstanceOf(AppError);
            expect(err.code).toBe(ErrorCode.PERMISSION_DENIED);
        }
        finally {
            // Restore permissions for cleanup
            await chmod(restrictedDir, 0o755);
        }
    });
});
//# sourceMappingURL=validate-project-path.test.js.map