// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import {
    unitTest,
    assert,
    assertEquals,
    assertThrows,
    assertThrowsAsync,
} from "./test_util.ts";

function assertDirectory(path: string, mode?: number): void {
    const info = Deno.lstatSync(path);
    assert(info.isDirectory);
    if (Deno.build.os !== "windows" && mode !== undefined) {
        assertEquals(info.mode! & 0o777, mode & ~Deno.umask());
    }
}

unitTest(
    { perms: { read: true, write: true } },
    function mkdirSyncSuccess(): void {
        const path = Deno.makeTempDirSync() + "/dir";
        Deno.mkdirSync(path);
        assertDirectory(path);
    }
);

unitTest(
    { perms: { read: true, write: true } },
    function mkdirSyncMode(): void {
        const path = Deno.makeTempDirSync() + "/dir";
        Deno.mkdirSync(path, { mode: 0o737 });
        assertDirectory(path, 0o737);
    }
);

unitTest({ perms: { write: false } }, function mkdirSyncPerm(): void {
    assertThrows(() => {
        Deno.mkdirSync("/baddir");
    }, Deno.errors.PermissionDenied);
});

unitTest(
    { perms: { read: true, write: true } },
    async function mkdirSuccess(): Promise<void> {
        const path = Deno.makeTempDirSync() + "/dir";
        await Deno.mkdir(path);
        assertDirectory(path);
    }
);

unitTest(
    { perms: { read: true, write: true } },
    async function mkdirMode(): Promise<void> {
        const path = Deno.makeTempDirSync() + "/dir";
        await Deno.mkdir(path, { mode: 0o737 });
        assertDirectory(path, 0o737);
    }
);

unitTest({ perms: { write: true } }, function mkdirErrSyncIfExists(): void {
    assertThrows(() => {
        Deno.mkdirSync(".");
    }, Deno.errors.AlreadyExists);
});

unitTest(
    { perms: { write: true } },
    async function mkdirErrIfExists(): Promise<void> {
        await assertThrowsAsync(async () => {
            await Deno.mkdir(".");
        }, Deno.errors.AlreadyExists);
    }
);

unitTest(
    { perms: { read: true, write: true } },
    function mkdirSyncRecursive(): void {
        const path = Deno.makeTempDirSync() + "/nested/directory";
        Deno.mkdirSync(path, { recursive: true });
        assertDirectory(path);
    }
);

unitTest(
    { perms: { read: true, write: true } },
    async function mkdirRecursive(): Promise<void> {
        const path = Deno.makeTempDirSync() + "/nested/directory";
        await Deno.mkdir(path, { recursive: true });
        assertDirectory(path);
    }
);

unitTest(
    { perms: { read: true, write: true } },
    function mkdirSyncRecursiveMode(): void {
        const nested = Deno.makeTempDirSync() + "/nested";
        const path = nested + "/dir";
        Deno.mkdirSync(path, { mode: 0o737, recursive: true });
        assertDirectory(path, 0o737);
        assertDirectory(nested, 0o737);
    }
);

unitTest(
    { perms: { read: true, write: true } },
    async function mkdirRecursiveMode(): Promise<void> {
        const nested = Deno.makeTempDirSync() + "/nested";
        const path = nested + "/dir";
        await Deno.mkdir(path, { mode: 0o737, recursive: true });
        assertDirectory(path, 0o737);
        assertDirectory(nested, 0o737);
    }
);

unitTest(
    { perms: { read: true, write: true } },
    function mkdirSyncRecursiveIfExists(): void {
        const path = Deno.makeTempDirSync() + "/dir";
        Deno.mkdirSync(path, { mode: 0o737 });
        Deno.mkdirSync(path, { recursive: true });
        Deno.mkdirSync(path, { recursive: true, mode: 0o731 });
        assertDirectory(path, 0o737);
        if (Deno.build.os !== "windows") {
            const pathLink = path + "Link";
            Deno.symlinkSync(path, pathLink);
            Deno.mkdirSync(pathLink, { recursive: true });
            Deno.mkdirSync(pathLink, { recursive: true, mode: 0o731 });
            assertDirectory(path, 0o737);
        }
    }
);

unitTest(
    { perms: { read: true, write: true } },
    async function mkdirRecursiveIfExists(): Promise<void> {
        const path = Deno.makeTempDirSync() + "/dir";
        await Deno.mkdir(path, { mode: 0o737 });
        await Deno.mkdir(path, { recursive: true });
        await Deno.mkdir(path, { recursive: true, mode: 0o731 });
        assertDirectory(path, 0o737);
        if (Deno.build.os !== "windows") {
            const pathLink = path + "Link";
            Deno.symlinkSync(path, pathLink);
            await Deno.mkdir(pathLink, { recursive: true });
            await Deno.mkdir(pathLink, { recursive: true, mode: 0o731 });
            assertDirectory(path, 0o737);
        }
    }
);

unitTest(
    { perms: { read: true, write: true } },
    function mkdirSyncErrors(): void {
        const testDir = Deno.makeTempDirSync();
        const emptydir = testDir + "/empty";
        const fulldir = testDir + "/dir";
        const file = fulldir + "/file";
        Deno.mkdirSync(emptydir);
        Deno.mkdirSync(fulldir);
        Deno.createSync(file).close();

        assertThrows((): void => {
            Deno.mkdirSync(emptydir, { recursive: false });
        }, Deno.errors.AlreadyExists);
        assertThrows((): void => {
            Deno.mkdirSync(fulldir, { recursive: false });
        }, Deno.errors.AlreadyExists);
        assertThrows((): void => {
            Deno.mkdirSync(file, { recursive: false });
        }, Deno.errors.AlreadyExists);
        assertThrows((): void => {
            Deno.mkdirSync(file, { recursive: true });
        }, Deno.errors.AlreadyExists);

        if (Deno.build.os !== "windows") {
            const fileLink = testDir + "/fileLink";
            const dirLink = testDir + "/dirLink";
            const danglingLink = testDir + "/danglingLink";
            Deno.symlinkSync(file, fileLink);
            Deno.symlinkSync(emptydir, dirLink);
            Deno.symlinkSync(testDir + "/nonexistent", danglingLink);

            assertThrows((): void => {
                Deno.mkdirSync(dirLink, { recursive: false });
            }, Deno.errors.AlreadyExists);
            assertThrows((): void => {
                Deno.mkdirSync(fileLink, { recursive: false });
            }, Deno.errors.AlreadyExists);
            assertThrows((): void => {
                Deno.mkdirSync(fileLink, { recursive: true });
            }, Deno.errors.AlreadyExists);
            assertThrows((): void => {
                Deno.mkdirSync(danglingLink, { recursive: false });
            }, Deno.errors.AlreadyExists);
            assertThrows((): void => {
                Deno.mkdirSync(danglingLink, { recursive: true });
            }, Deno.errors.AlreadyExists);
        }
    }
);
