1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-20 22:34:46 -05:00
denoland-deno/tests/unit_node/fs_test.ts
Marvin Hagemeister 927352bd4e
fix(node/fs): add missing stat path argument validation (#27086)
We didn't validate the `path` argument that's passed to `fs.stat()` and
`fs.statSync()` which lead to wrong errors being thrown. The
`@rollup/plugin-node-resolve` code calls it with `undefined` quite a lot
which lead to `nitro` and `nuxt` failing.

Fixes https://github.com/denoland/deno/issues/26700

---------

Co-authored-by: Yoshiya Hinosawa <stibium121@gmail.com>
2024-11-27 08:35:39 +01:00

292 lines
7.2 KiB
TypeScript

// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
/// <reference lib="deno.ns" />
import { assert, assertEquals, assertRejects, assertThrows } from "@std/assert";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { tmpdir } from "node:os";
import {
closeSync,
constants,
copyFileSync,
createWriteStream,
existsSync,
lstatSync,
mkdtempSync,
openSync,
promises,
readFileSync,
readSync,
Stats,
statSync,
writeFileSync,
} from "node:fs";
import {
constants as fsPromiseConstants,
copyFile,
cp,
FileHandle,
open,
stat,
writeFile,
} from "node:fs/promises";
import process from "node:process";
import { pathToAbsoluteFileUrl } from "../unit/test_util.ts";
Deno.test(
"[node/fs writeFileSync] write file without option",
() => {
const data = "Hello";
const filename = mkdtempSync(join(tmpdir(), "foo-")) + "/test.txt";
writeFileSync(filename, data);
const dataRead = readFileSync(filename, "utf8");
assert(dataRead === "Hello");
},
);
Deno.test(
"[node/fs writeFileSync] write file with option ASCII",
() => {
const data = "Hello";
const filename = mkdtempSync(join(tmpdir(), "foo-")) + "/test.txt";
writeFileSync(filename, data, { encoding: "ascii" });
const dataRead = readFileSync(filename, "utf8");
assert(dataRead === "Hello");
},
);
Deno.test(
"[node/fs writeFileSync] write file throws error when encoding is not implemented",
() => {
const data = "Hello";
const filename = mkdtempSync(join(tmpdir(), "foo-")) + "/test.txt";
assertThrows(
() => writeFileSync(filename, data, { encoding: "utf16le" }),
'The value "utf16le" is invalid for option "encoding"',
);
},
);
Deno.test(
"[node/fs existsSync] path",
{ permissions: { read: true } },
() => {
assert(existsSync("tests/testdata/assets/fixture.json"));
},
);
Deno.test(
"[node/fs existsSync] url",
{ permissions: { read: true } },
() => {
assert(existsSync(
pathToAbsoluteFileUrl("tests/testdata/assets/fixture.json"),
));
},
);
Deno.test(
"[node/fs existsSync] no permission",
{ permissions: { read: false } },
() => {
assertThrows(() => {
existsSync("tests/testdata/assets/fixture.json");
}, Deno.errors.NotCapable);
},
);
Deno.test(
"[node/fs existsSync] not exists",
{ permissions: { read: true } },
() => {
assert(!existsSync("bad_filename"));
},
);
Deno.test(
"[node/fs/promises constants] is the same as from node:fs",
() => {
assertEquals(constants, fsPromiseConstants);
assertEquals(constants, promises.constants);
},
);
Deno.test(
"[node/fs statSync] instanceof fs.Stats",
() => {
const stat = statSync("tests/testdata/assets/fixture.json");
assert(stat);
assert(stat instanceof Stats);
},
);
Deno.test(
"[node/fs statSync] throw error with path information",
() => {
const file = "non-exist-file";
const fileUrl = new URL(file, import.meta.url);
assertThrows(() => {
statSync(file);
}, "Error: ENOENT: no such file or directory, stat 'non-exist-file'");
assertThrows(() => {
statSync(fileUrl);
}, `Error: ENOENT: no such file or directory, stat '${fileUrl.pathname}'`);
},
);
Deno.test(
"[node/fs/promises stat] throw error with path information",
async () => {
const file = "non-exist-file";
const fileUrl = new URL(file, import.meta.url);
try {
await stat(file);
} catch (error: unknown) {
assertEquals(
`${error}`,
"Error: ENOENT: no such file or directory, stat 'non-exist-file'",
);
}
try {
await stat(fileUrl);
} catch (error: unknown) {
assertEquals(
`${error}`,
`Error: ENOENT: no such file or directory, stat '${
fileURLToPath(fileUrl)
}'`,
);
}
},
);
Deno.test(
"[node/fs/promises cp] copy file",
async () => {
const src = mkdtempSync(join(tmpdir(), "foo-")) + "/test.txt";
const dest = mkdtempSync(join(tmpdir(), "foo-")) + "/test.txt";
writeFileSync(src, "Hello");
await cp(src, dest);
const dataRead = readFileSync(dest, "utf8");
assert(dataRead === "Hello");
},
);
// TODO(kt3k): Delete this test case, and instead enable the compat case
// `test/parallel/test-fs-writestream-open-write.js`, when we update
// `tests/node_compat/runner/suite`.
Deno.test("[node/fs createWriteStream", async () => {
const { promise, resolve, reject } = Promise.withResolvers<void>();
const tempDir = await Deno.makeTempDir();
const file = join(tempDir, "file.txt");
try {
const w = createWriteStream(file);
w.on("open", () => {
w.write("hello, ");
process.nextTick(() => {
w.write("world");
w.end();
});
});
w.on("close", async () => {
try {
assertEquals(await Deno.readTextFile(file), "hello, world");
resolve();
} catch (e) {
reject(e);
}
});
await promise;
} finally {
await Deno.remove(tempDir, { recursive: true });
}
});
Deno.test(
"[node/fs lstatSync] supports throwIfNoEntry option",
() => {
const result = lstatSync("non-existing-path", { throwIfNoEntry: false });
assertEquals(result, undefined);
},
);
// Test for https://github.com/denoland/deno/issues/23707
Deno.test(
"[node/fs/promises read] respect position argument",
async () => {
const file = mkdtempSync(join(tmpdir(), "foo-")) + "/test.bin";
await writeFile(file, "");
const res: number[] = [];
let fd: FileHandle | undefined;
try {
fd = await open(file, "r+");
for (let i = 0; i <= 5; i++) {
const buffer = new Uint8Array([i]);
await fd.write(buffer, 0, 1, i + 10);
}
for (let i = 10; i <= 15; i++) {
const buffer = new Uint8Array(1);
await fd.read(buffer, 0, 1, i);
res.push(Number(buffer.toString()));
}
} finally {
await fd?.close();
}
assertEquals(res, [0, 1, 2, 3, 4, 5]);
},
);
Deno.test("[node/fs] readSync works", () => {
const fd = openSync("tests/testdata/assets/hello.txt", "r");
const buf = new Uint8Array(256);
const bytesRead = readSync(fd!, buf);
assertEquals(bytesRead, 12);
closeSync(fd!);
});
Deno.test("[node/fs] copyFile COPYFILE_EXCL works", async () => {
const dir = mkdtempSync(join(tmpdir(), "foo-"));
const src = join(dir, "src.txt");
const dest = join(dir, "dest.txt");
await writeFile(src, "");
await copyFile(src, dest, fsPromiseConstants.COPYFILE_EXCL);
assert(existsSync(dest));
await assertRejects(() =>
copyFile(src, dest, fsPromiseConstants.COPYFILE_EXCL)
);
const dest2 = join(dir, "dest2.txt");
copyFileSync(src, dest2, fsPromiseConstants.COPYFILE_EXCL);
assert(existsSync(dest2));
assertThrows(() =>
copyFileSync(src, dest2, fsPromiseConstants.COPYFILE_EXCL)
);
});
Deno.test("[node/fs] statSync throws ENOENT for invalid path containing colon in it", () => {
// deno-lint-ignore no-explicit-any
const err: any = assertThrows(() => {
// Note: Deno.stat throws ERROR_INVALID_NAME (os error 123) instead of
// ERROR_FILE_NOT_FOUND (os error 2) on windows. This case checks that
// ERROR_INVALID_NAME is mapped to ENOENT correctly on node compat layer.
statSync("jsr:@std/assert");
});
assertEquals(err.code, "ENOENT");
});