mirror of
https://github.com/renovatebot/renovate.git
synced 2026-11-02 18:43:40 +00:00
8e19aef903
Two `if` statements had their implicit else path untested: - deleteLocalFile: the no-localDir case never ran - getMergeConfidenceLevel: the API returning an unrecognised confidence value never ran, so the 'neutral' fallback was untested Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
689 lines
23 KiB
TypeScript
689 lines
23 KiB
TypeScript
import { findUp as _findUp } from 'find-up';
|
|
import fs from 'fs-extra';
|
|
import type { DirectoryResult } from 'tmp-promise';
|
|
import tmp from 'tmp-promise';
|
|
import upath from 'upath';
|
|
import { logger } from '~test/util.ts';
|
|
import { GlobalConfig } from '../../config/global.ts';
|
|
import {
|
|
cachePathExists,
|
|
cachePathIsFile,
|
|
chmodLocalFile,
|
|
createCacheReadStream,
|
|
createCacheWriteStream,
|
|
deleteLocalFile,
|
|
ensureCacheDir,
|
|
ensureDir,
|
|
ensureLocalDir,
|
|
findLocalSiblingOrParent,
|
|
findUpLocal,
|
|
getLocalFiles,
|
|
getParentDir,
|
|
getSiblingFileName,
|
|
isValidLocalPath,
|
|
listCacheDir,
|
|
localPathExists,
|
|
localPathIsFile,
|
|
localPathIsSymbolicLink,
|
|
outputCacheFile,
|
|
privateCacheDir,
|
|
readCacheFile,
|
|
readLocalDirectory,
|
|
readLocalFile,
|
|
readLocalSymlink,
|
|
readSystemFile,
|
|
renameCacheFile,
|
|
renameLocalFile,
|
|
rmCache,
|
|
statCacheFile,
|
|
statLocalFile,
|
|
writeLocalFile,
|
|
writeSystemFile,
|
|
} from './index.ts';
|
|
|
|
vi.mock('../exec/env.ts');
|
|
vi.mock('find-up');
|
|
const findUp = vi.mocked(_findUp);
|
|
|
|
describe('util/fs/index', () => {
|
|
let localDirResult: DirectoryResult;
|
|
let localDir: string;
|
|
|
|
let cacheDirResult: DirectoryResult;
|
|
let cacheDir: string;
|
|
|
|
let tmpDirResult: DirectoryResult;
|
|
let tmpDir: string;
|
|
|
|
beforeEach(async () => {
|
|
localDirResult = await tmp.dir({ unsafeCleanup: true });
|
|
localDir = localDirResult.path;
|
|
|
|
cacheDirResult = await tmp.dir({ unsafeCleanup: true });
|
|
cacheDir = cacheDirResult.path;
|
|
|
|
tmpDirResult = await tmp.dir({ unsafeCleanup: true });
|
|
tmpDir = tmpDirResult.path;
|
|
|
|
GlobalConfig.set({ localDir, cacheDir });
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await localDirResult?.cleanup();
|
|
await cacheDirResult?.cleanup();
|
|
await tmpDirResult?.cleanup();
|
|
});
|
|
|
|
describe('getParentDir', () => {
|
|
it.each`
|
|
dir | expected
|
|
${'/foo/bar/'} | ${'/foo'}
|
|
${'/foo/bar'} | ${'/foo'}
|
|
${'/foo/'} | ${'/'}
|
|
${'/foo'} | ${'/'}
|
|
${'foo/bar/'} | ${'foo'}
|
|
${'foo/bar'} | ${'foo'}
|
|
${'foo/'} | ${''}
|
|
${'foo'} | ${''}
|
|
${''} | ${''}
|
|
${'.'} | ${''}
|
|
${'..'} | ${''}
|
|
${'./foo'} | ${'.'}
|
|
${'../foo'} | ${'..'}
|
|
`(`('$dir') -> '$expected'`, ({ dir, expected }) => {
|
|
expect(getParentDir(dir)).toBe(expected);
|
|
});
|
|
});
|
|
|
|
describe('getSiblingFileName', () => {
|
|
it.each`
|
|
file | sibling | expected
|
|
${'/foo/bar'} | ${'baz'} | ${'/foo/baz'}
|
|
${'foo/bar'} | ${'baz'} | ${'foo/baz'}
|
|
${'foo/'} | ${'baz'} | ${'baz'}
|
|
${'foo'} | ${'baz'} | ${'baz'}
|
|
${'./foo'} | ${'baz'} | ${'baz'}
|
|
${'../foo'} | ${'baz'} | ${'../baz'}
|
|
`(`('$file', '$sibling') -> '$expected'`, ({ file, sibling, expected }) => {
|
|
expect(getSiblingFileName(file, sibling)).toBe(expected);
|
|
});
|
|
});
|
|
|
|
describe('readLocalFile', () => {
|
|
it('reads buffer', async () => {
|
|
await fs.outputFile(`${localDir}/file.txt`, 'foobar');
|
|
const res = await readLocalFile('file.txt');
|
|
expect(res).toBeInstanceOf(Buffer);
|
|
});
|
|
|
|
it('reads string', async () => {
|
|
await fs.outputFile(`${localDir}/file.txt`, 'foobar');
|
|
const res = await readLocalFile('file.txt', 'utf8');
|
|
expect(res).toBe('foobar');
|
|
});
|
|
|
|
it('returns null if file is not found', async () => {
|
|
await expect(readLocalFile('foobar')).resolves.toBeNull();
|
|
});
|
|
|
|
it('logs a warning if hidden Unciode characters are found', async () => {
|
|
await fs.outputFile(`${localDir}/file.txt`, 'some\u00A0content\u200Bfoo');
|
|
await readLocalFile('file.txt', 'utf8');
|
|
|
|
// NOTE that we cannot test that this is called once across multiple `readLocalFile` as we're mocking the logger, so we're testing that it's called, not validating the nubmer of times
|
|
expect(logger.logger.once.warn).toHaveBeenCalledWith(
|
|
{ file: 'file.txt', hiddenCharacters: '\\u00A0\\u200B' },
|
|
'Hidden Unicode characters have been discovered in file(s) in your repository. See your Renovate logs for more details. Please confirm that they are intended to be there, as they could be an attempt to "smuggle" text into your codebase, or used to confuse tools like Renovate or Large Language Models (LLMs)',
|
|
);
|
|
});
|
|
|
|
it('does not log the same hidden Unciode characters if found multiple times', async () => {
|
|
await fs.outputFile(
|
|
`${localDir}/file.txt`,
|
|
'some\u00A0content\u200Bfoo\u200Bbaz',
|
|
);
|
|
await readLocalFile('file.txt', 'utf8');
|
|
|
|
expect(logger.logger.once.warn).toHaveBeenCalledWith(
|
|
{ file: 'file.txt', hiddenCharacters: '\\u00A0\\u200B' },
|
|
'Hidden Unicode characters have been discovered in file(s) in your repository. See your Renovate logs for more details. Please confirm that they are intended to be there, as they could be an attempt to "smuggle" text into your codebase, or used to confuse tools like Renovate or Large Language Models (LLMs)',
|
|
);
|
|
});
|
|
|
|
it('logs a trace message (not warning) if hidden Unicode characters are found in a binary file', async () => {
|
|
// Create a binary file with null bytes (making it binary) and zero-width space (U+200B)
|
|
const binaryContent = Buffer.from([
|
|
0x50, 0x4b, 0x03, 0x04, 0x00, 0x00, 0xe2, 0x80, 0x8b, 0x00,
|
|
]); // 0xe2 0x80 0x8b is UTF-8 encoding of U+200B (zero-width space)
|
|
await fs.outputFile(`${localDir}/binary.zip`, binaryContent);
|
|
await readLocalFile('binary.zip');
|
|
|
|
expect(logger.logger.once.warn).toHaveBeenCalledTimes(0);
|
|
expect(logger.logger.trace).toHaveBeenCalledWith(
|
|
{
|
|
file: 'binary.zip',
|
|
hiddenCharacters: expect.stringContaining('\\u200B'),
|
|
},
|
|
'Hidden Unicode characters discovered in file `binary.zip`, but not logging higher than TRACE as it appears to be a binary file',
|
|
);
|
|
});
|
|
|
|
// Via https://github.com/renovatebot/renovate/discussions/41381 and https://github.com/renovatebot/renovate/pull/41353, the Byte Order Mark (BOM) character is common on Windows-based platforms
|
|
describe('if hidden Byte Order Mark (BOM) Unciode characters are found', () => {
|
|
it('but no other hidden characters, it logs a trace message', async () => {
|
|
// via https://github.com/Particular/Particular.Packaging/blob/be9e3f4cb60bba9a4dd06dbc9f25fb7ff152e126/src/Particular.Packaging.Tasks/Particular.Packaging.Tasks.csproj
|
|
await fs.outputFile(
|
|
`${localDir}/example.csproj`,
|
|
'\uFEFF<Project Sdk="Microsoft.NET.Sdk">',
|
|
);
|
|
await readLocalFile('example.csproj', 'utf8');
|
|
|
|
expect(logger.logger.once.warn).toHaveBeenCalledTimes(0);
|
|
expect(logger.logger.once.trace).toHaveBeenCalledWith(
|
|
{ file: 'example.csproj', hiddenCharacters: '\\uFEFF' },
|
|
'Hidden Byte Order Mark (BOM) Unicode characters has been discovered in the file `example.csproj`. This is likely safe, if you are using Microsoft Windows, but please confirm that they are intended to be there, as they could be an attempt to "smuggle" text into your codebase, or used to confuse tools like Renovate or Large Language Models (LLMs)',
|
|
);
|
|
});
|
|
|
|
it('as well as other hidden characters, it logs a warning', async () => {
|
|
await fs.outputFile(
|
|
`${localDir}/example.csproj`,
|
|
'\uFEFF<Project Sdk="Microsoft.NET.Sdk">\u200B',
|
|
);
|
|
await readLocalFile('example.csproj', 'utf8');
|
|
|
|
expect(logger.logger.once.warn).toHaveBeenCalledWith(
|
|
{ file: 'example.csproj', hiddenCharacters: '\\uFEFF\\u200B' },
|
|
'Hidden Unicode characters have been discovered in file(s) in your repository. See your Renovate logs for more details. Please confirm that they are intended to be there, as they could be an attempt to "smuggle" text into your codebase, or used to confuse tools like Renovate or Large Language Models (LLMs)',
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('writeLocalFile', () => {
|
|
it('outputs file', async () => {
|
|
await writeLocalFile('foo/bar/file.txt', 'foobar');
|
|
|
|
const path = `${localDir}/foo/bar/file.txt`;
|
|
await expect(fs.pathExists(path)).resolves.toBeTrue();
|
|
await expect(fs.readFile(path, 'utf8')).resolves.toBe('foobar');
|
|
});
|
|
});
|
|
|
|
describe('deleteLocalFile', () => {
|
|
it('throws if platform is local', async () => {
|
|
GlobalConfig.set({ platform: 'local' });
|
|
await expect(deleteLocalFile('foo/bar/file.txt')).rejects.toThrow(
|
|
'Cannot delete file when platform=local',
|
|
);
|
|
});
|
|
|
|
it('deletes file', async () => {
|
|
const filePath = `${localDir}/foo/bar/file.txt`;
|
|
await fs.outputFile(filePath, 'foobar');
|
|
|
|
await expect(fs.pathExists(filePath)).resolves.toBeTrue();
|
|
await deleteLocalFile('foo/bar/file.txt');
|
|
await expect(fs.pathExists(filePath)).resolves.toBeFalse();
|
|
});
|
|
|
|
it('does nothing if no localDir is configured', async () => {
|
|
const filePath = `${localDir}/foo/bar/file.txt`;
|
|
await fs.outputFile(filePath, 'foobar');
|
|
GlobalConfig.set({});
|
|
|
|
await expect(
|
|
deleteLocalFile('foo/bar/file.txt'),
|
|
).resolves.toBeUndefined();
|
|
await expect(fs.pathExists(filePath)).resolves.toBeTrue();
|
|
});
|
|
});
|
|
|
|
describe('renameLocalFile', () => {
|
|
it('renames file', async () => {
|
|
const sourcePath = `${localDir}/foo.txt`;
|
|
const targetPath = `${localDir}/bar.txt`;
|
|
await fs.outputFile(sourcePath, 'foobar');
|
|
|
|
await expect(fs.pathExists(sourcePath)).resolves.toBeTrue();
|
|
await expect(fs.pathExists(targetPath)).resolves.toBeFalse();
|
|
await renameLocalFile('foo.txt', 'bar.txt');
|
|
await expect(fs.pathExists(sourcePath)).resolves.toBeFalse();
|
|
await expect(fs.pathExists(targetPath)).resolves.toBeTrue();
|
|
});
|
|
});
|
|
|
|
describe('renameCacheFile', () => {
|
|
it('renames file and replaces existing target', async () => {
|
|
const sourcePath = `${cacheDir}/foo.txt`;
|
|
const targetPath = `${cacheDir}/bar.txt`;
|
|
await fs.outputFile(sourcePath, 'source');
|
|
await fs.outputFile(targetPath, 'target');
|
|
|
|
await expect(fs.pathExists(sourcePath)).resolves.toBeTrue();
|
|
await expect(fs.readFile(targetPath, 'utf8')).resolves.toBe('target');
|
|
await renameCacheFile('foo.txt', 'bar.txt');
|
|
await expect(fs.pathExists(sourcePath)).resolves.toBeFalse();
|
|
await expect(fs.readFile(targetPath, 'utf8')).resolves.toBe('source');
|
|
});
|
|
});
|
|
|
|
describe('ensureDir', () => {
|
|
it('creates directory', async () => {
|
|
const path = `${localDir}/foo/bar`;
|
|
|
|
await ensureDir(path);
|
|
const stat = await fs.stat(path);
|
|
expect(stat.isDirectory()).toBeTrue();
|
|
});
|
|
});
|
|
|
|
describe('ensureLocalDir', () => {
|
|
it('creates local directory', async () => {
|
|
const path = `${localDir}/foo/bar`;
|
|
|
|
await ensureLocalDir('foo/bar');
|
|
const stat = await fs.stat(path);
|
|
expect(stat.isDirectory()).toBeTrue();
|
|
});
|
|
});
|
|
|
|
describe('ensureCacheDir', () => {
|
|
it('prefers environment variables over global config', async () => {
|
|
const res = await ensureCacheDir('bundler');
|
|
const path = upath.join(cacheDir, 'others/bundler');
|
|
expect(res).toEqual(path);
|
|
await expect(fs.pathExists(path)).resolves.toBeTrue();
|
|
});
|
|
});
|
|
|
|
describe('privateCacheDir', () => {
|
|
it('returns cache dir', () => {
|
|
GlobalConfig.set({ cacheDir: '/tmp/foo/bar' });
|
|
expect(privateCacheDir()).toBe(`/tmp/foo/bar/__renovate-private-cache`);
|
|
});
|
|
});
|
|
|
|
describe('localPathExists', () => {
|
|
it('returns true for file', async () => {
|
|
const path = `${localDir}/file.txt`;
|
|
await fs.outputFile(path, 'foobar');
|
|
await expect(localPathExists('file.txt')).resolves.toBeTrue();
|
|
});
|
|
|
|
it('returns true for directory', async () => {
|
|
await expect(localPathExists('.')).resolves.toBeTrue();
|
|
});
|
|
|
|
it('returns false', async () => {
|
|
await expect(localPathExists('file.txt')).resolves.toBeFalse();
|
|
});
|
|
});
|
|
|
|
describe('isLocalPath', () => {
|
|
it('returns true for valid local path', () => {
|
|
expect(isValidLocalPath('./foo/...')).toBeTrue();
|
|
});
|
|
|
|
it('returns false', () => {
|
|
expect(isValidLocalPath('/file.txt')).toBeFalse();
|
|
});
|
|
});
|
|
|
|
describe('readLocalSymlink', () => {
|
|
it('reads symlink', async () => {
|
|
await writeLocalFile('test/test.txt', '');
|
|
await fs.symlink(
|
|
upath.join(localDir, 'test/test.txt'),
|
|
upath.join(localDir, 'test/test'),
|
|
);
|
|
|
|
const result = await readLocalSymlink('test/test');
|
|
|
|
expect(result).not.toBeNull();
|
|
});
|
|
|
|
it('return null when link not exists', async () => {
|
|
await writeLocalFile('test/test.txt', '');
|
|
await fs.symlink(
|
|
upath.join(localDir, 'test/test.txt'),
|
|
upath.join(localDir, 'test/test'),
|
|
);
|
|
|
|
const notExistsResult = await readLocalSymlink('test/not-exists');
|
|
|
|
expect(notExistsResult).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('findLocalSiblingOrParent', () => {
|
|
it('returns path for file', async () => {
|
|
await writeLocalFile('crates/one/Cargo.toml', 'foo');
|
|
await writeLocalFile('Cargo.lock', 'bar');
|
|
|
|
await expect(
|
|
findLocalSiblingOrParent('crates/one/Cargo.toml', 'Cargo.lock'),
|
|
).resolves.toBe('Cargo.lock');
|
|
await expect(
|
|
findLocalSiblingOrParent('crates/one/Cargo.toml', 'Cargo.mock'),
|
|
).resolves.toBeNull();
|
|
|
|
await writeLocalFile('crates/one/Cargo.lock', '');
|
|
|
|
await expect(
|
|
findLocalSiblingOrParent('crates/one/Cargo.toml', 'Cargo.lock'),
|
|
).resolves.toBe('crates/one/Cargo.lock');
|
|
await expect(
|
|
findLocalSiblingOrParent('crates/one', 'Cargo.lock'),
|
|
).resolves.toBe('Cargo.lock');
|
|
await expect(
|
|
findLocalSiblingOrParent('crates/one/Cargo.toml', 'Cargo.mock'),
|
|
).resolves.toBeNull();
|
|
});
|
|
|
|
it('immediately returns null when either path is absolute', async () => {
|
|
await expect(
|
|
findLocalSiblingOrParent('/etc/hosts', 'other'),
|
|
).resolves.toBeNull();
|
|
await expect(
|
|
findLocalSiblingOrParent('other', '/etc/hosts'),
|
|
).resolves.toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('readLocalDirectory', () => {
|
|
it('returns dir content', async () => {
|
|
await writeLocalFile('test/Cargo.toml', '');
|
|
await writeLocalFile('test/Cargo.lock', '');
|
|
|
|
const result = await readLocalDirectory('test');
|
|
expect(result).toEqual(['Cargo.lock', 'Cargo.toml']);
|
|
|
|
await writeLocalFile('Cargo.lock', '');
|
|
await writeLocalFile('test/subdir/Cargo.lock', '');
|
|
|
|
const resultWithAdditionalFiles = await readLocalDirectory('test');
|
|
expect(resultWithAdditionalFiles).toEqual([
|
|
'Cargo.lock',
|
|
'Cargo.toml',
|
|
'subdir',
|
|
]);
|
|
});
|
|
|
|
it('return empty array for non existing directory', async () => {
|
|
await expect(readLocalDirectory('somedir')).rejects.toThrow(
|
|
'ENOENT: no such file or directory',
|
|
);
|
|
});
|
|
|
|
it('return empty array for a existing but empty directory', async () => {
|
|
await ensureLocalDir('somedir');
|
|
const result = await readLocalDirectory('somedir');
|
|
expect(result).not.toBeNull();
|
|
expect(result).toBeArrayOfSize(0);
|
|
});
|
|
});
|
|
|
|
describe('createCacheWriteStream', () => {
|
|
it('creates write stream', async () => {
|
|
const path = `${cacheDir}/file.txt`;
|
|
await fs.outputFile(path, 'foo');
|
|
|
|
const stream = createCacheWriteStream('file.txt');
|
|
expect(stream).toBeInstanceOf(fs.WriteStream);
|
|
|
|
const write = new Promise((resolve) => {
|
|
stream.write('bar');
|
|
stream.close(resolve);
|
|
});
|
|
await write;
|
|
await expect(fs.readFile(path, 'utf8')).resolves.toBe('bar');
|
|
});
|
|
});
|
|
|
|
describe('createCacheReadStream', () => {
|
|
it('creates read stream', async () => {
|
|
const path = `${cacheDir}/file.txt`;
|
|
const fileContent = 'foo';
|
|
await fs.outputFile(path, fileContent);
|
|
|
|
const stream = createCacheReadStream('file.txt');
|
|
expect(stream).toBeInstanceOf(fs.ReadStream);
|
|
|
|
let data = '';
|
|
stream.on('data', (chunk) => {
|
|
data += chunk.toString();
|
|
});
|
|
|
|
await new Promise<void>((resolve, reject) => {
|
|
stream.on('end', resolve);
|
|
stream.on('error', reject);
|
|
});
|
|
|
|
expect(data).toBe(fileContent);
|
|
});
|
|
});
|
|
|
|
describe('localPathIsFile', () => {
|
|
it('returns true for file', async () => {
|
|
const path = `${localDir}/file.txt`;
|
|
await fs.outputFile(path, 'foo');
|
|
await expect(localPathIsFile('file.txt')).resolves.toBeTrue();
|
|
});
|
|
|
|
it('returns false for directory', async () => {
|
|
const path = upath.resolve(`${localDir}/foobar`);
|
|
await fs.mkdir(path);
|
|
await expect(localPathIsFile(path)).resolves.toBeFalse();
|
|
});
|
|
|
|
it('returns false for non-existing path', async () => {
|
|
await expect(
|
|
localPathIsFile(upath.resolve(`${localDir}/foobar`)),
|
|
).resolves.toBeFalse();
|
|
});
|
|
});
|
|
|
|
describe('localPathIsSymbolicLink', () => {
|
|
it('returns false for file', async () => {
|
|
const path = `${localDir}/file.txt`;
|
|
await fs.outputFile(path, 'foobar');
|
|
await expect(localPathIsSymbolicLink(path)).resolves.toBeFalse();
|
|
});
|
|
|
|
it('returns false for directory', async () => {
|
|
const path = `${localDir}/foobar`;
|
|
await fs.mkdir(path);
|
|
await expect(localPathIsSymbolicLink(path)).resolves.toBeFalse();
|
|
});
|
|
|
|
it('returns false for non-existing path', async () => {
|
|
const path = `${localDir}/file.txt`;
|
|
await expect(localPathIsSymbolicLink(path)).resolves.toBeFalse();
|
|
});
|
|
|
|
it('returns true for symlink', async () => {
|
|
const source = `${localDir}/test/test.txt`;
|
|
const target = `${localDir}/test/test`;
|
|
await fs.outputFile(source, 'foobar');
|
|
await fs.symlink(source, target);
|
|
|
|
const result = await localPathIsSymbolicLink('test/test');
|
|
expect(result).toBeTrue();
|
|
});
|
|
});
|
|
|
|
describe('findUpLocal', () => {
|
|
beforeEach(() => {
|
|
GlobalConfig.set({ localDir: '/abs/path/to/local/dir' });
|
|
});
|
|
|
|
it('returns relative path for file', async () => {
|
|
findUp.mockResolvedValueOnce('/abs/path/to/local/dir/subdir/file.json');
|
|
const res = await findUpLocal('file.json', 'subdir/subdir2');
|
|
expect(res).toBe('subdir/file.json');
|
|
});
|
|
|
|
it('returns null if nothing found', async () => {
|
|
findUp.mockResolvedValueOnce(undefined);
|
|
const res = await findUpLocal('file.json', 'subdir/subdir2');
|
|
expect(res).toBeNull();
|
|
});
|
|
|
|
it('returns undefined if found a file outside of localDir', async () => {
|
|
findUp.mockResolvedValueOnce('/abs/path/to/file.json');
|
|
const res = await findUpLocal('file.json', 'subdir/subdir2');
|
|
expect(res).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('chmodLocalFile', () => {
|
|
it('changes file mode', async () => {
|
|
await writeLocalFile('foo', 'bar');
|
|
let stat = await statLocalFile('foo');
|
|
const oldMode = stat!.mode & 0o777;
|
|
const newMode = oldMode & 0o555; // Remove `write` attributes (Windows-compatible)
|
|
|
|
await chmodLocalFile('foo', newMode);
|
|
stat = await statLocalFile('foo');
|
|
expect(stat!.mode & 0o777).toBe(newMode);
|
|
|
|
await chmodLocalFile('foo', oldMode);
|
|
stat = await statLocalFile('foo');
|
|
expect(stat!.mode & 0o777).toBe(oldMode);
|
|
});
|
|
});
|
|
|
|
describe('statLocalFile', () => {
|
|
it('returns stat object', async () => {
|
|
await expect(statLocalFile('foo')).resolves.toBeNull();
|
|
|
|
await writeLocalFile('foo', 'bar');
|
|
const stat = await statLocalFile('foo');
|
|
expect(stat).toBeTruthy();
|
|
expect(stat!.isFile()).toBeTrue();
|
|
});
|
|
});
|
|
|
|
describe('statCacheFile', () => {
|
|
it('returns stat object', async () => {
|
|
await expect(statCacheFile('foo')).resolves.toBeNull();
|
|
|
|
await fs.outputFile(`${cacheDir}/foo`, 'foobar');
|
|
const stat = await statCacheFile('foo');
|
|
expect(stat).toBeTruthy();
|
|
expect(stat!.isFile()).toBeTrue();
|
|
});
|
|
});
|
|
|
|
describe('listCacheDir', () => {
|
|
it('lists directory', async () => {
|
|
await fs.outputFile(`${cacheDir}/foo/bar.txt`, 'foobar');
|
|
await expect(listCacheDir('foo')).resolves.toEqual(['bar.txt']);
|
|
});
|
|
});
|
|
|
|
describe('rmCache', () => {
|
|
it('removes cache dir', async () => {
|
|
await fs.outputFile(`${cacheDir}/foo/bar/file.txt`, 'foobar');
|
|
await rmCache(`foo/bar`);
|
|
await expect(
|
|
fs.pathExists(`${cacheDir}/foo/bar/file.txt`),
|
|
).resolves.toBeFalse();
|
|
await expect(fs.pathExists(`${cacheDir}/foo/bar`)).resolves.toBeFalse();
|
|
});
|
|
|
|
it('ignores missing path', async () => {
|
|
await expect(rmCache(`foo/missing`)).resolves.toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('cachePathExists', () => {
|
|
it('reads file', async () => {
|
|
await fs.outputFile(`${cacheDir}/foo/bar/file.txt`, 'foobar');
|
|
await expect(cachePathExists(`foo/bar/file.txt1`)).resolves.toBeFalse();
|
|
await expect(cachePathExists(`foo/bar/file.txt`)).resolves.toBeTrue();
|
|
});
|
|
});
|
|
|
|
describe('cachePathIsFile', () => {
|
|
it('returns true for file', async () => {
|
|
await fs.outputFile(`${cacheDir}/foo/bar/file.txt`, 'foobar');
|
|
|
|
await expect(cachePathIsFile(`foo/bar/file.txt`)).resolves.toBeTrue();
|
|
});
|
|
|
|
it('returns false for directory', async () => {
|
|
await fs.ensureDir(`${cacheDir}/foo/bar`);
|
|
|
|
await expect(cachePathIsFile(`foo/bar`)).resolves.toBeFalse();
|
|
});
|
|
|
|
it('returns false if does not exist', async () => {
|
|
await expect(cachePathIsFile(`a/a/file.txt`)).resolves.toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('readCacheFile', () => {
|
|
it('reads file', async () => {
|
|
await fs.outputFile(`${cacheDir}/foo/bar/file.txt`, 'foobar');
|
|
await expect(readCacheFile(`foo/bar/file.txt`, 'utf8')).resolves.toBe(
|
|
'foobar',
|
|
);
|
|
await expect(readCacheFile(`foo/bar/file.txt`)).resolves.toEqual(
|
|
Buffer.from('foobar'),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('outputCacheFile', () => {
|
|
it('outputs file', async () => {
|
|
await outputCacheFile('file.txt', 'foobar');
|
|
const res = await fs.readFile(`${cacheDir}/file.txt`, 'utf8');
|
|
expect(res).toBe('foobar');
|
|
});
|
|
});
|
|
|
|
describe('readSystemFile', () => {
|
|
it('reads file', async () => {
|
|
const path = `${tmpDir}/file.txt`;
|
|
await fs.outputFile(path, 'foobar', { encoding: 'utf8' });
|
|
await expect(readSystemFile(path, 'utf8')).resolves.toBe('foobar');
|
|
await expect(readSystemFile(path)).resolves.toEqual(
|
|
Buffer.from('foobar'),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('writeSystemFile', () => {
|
|
it('writes file', async () => {
|
|
const path = `${tmpDir}/file.txt`;
|
|
await writeSystemFile(path, 'foobar');
|
|
await expect(readSystemFile(path)).resolves.toEqual(
|
|
Buffer.from('foobar'),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('getLocalFiles', () => {
|
|
it('reads list of files from local fs', async () => {
|
|
const fileContentMap = {
|
|
file1: 'foobar',
|
|
file2: 'foobar2',
|
|
};
|
|
|
|
await fs.outputFile(`${localDir}/file1`, fileContentMap.file1);
|
|
await fs.outputFile(`${localDir}/file2`, fileContentMap.file2);
|
|
const res = await getLocalFiles(Object.keys(fileContentMap));
|
|
expect(res).toStrictEqual(fileContentMap);
|
|
});
|
|
|
|
it('returns null as content if file is not found', async () => {
|
|
const res = await getLocalFiles(['invalidfile']);
|
|
expect(res).toStrictEqual({
|
|
invalidfile: null,
|
|
});
|
|
});
|
|
});
|
|
});
|