0
0
Fork 0
mirror of https://github.com/renovatebot/renovate.git synced 2025-02-21 15:47:15 +00:00
renovatebot_renovate/lib/workers/global/config/parse/file.spec.ts

234 lines
7.5 KiB
TypeScript
Raw Normal View History

import fs from 'node:fs';
import fsExtra from 'fs-extra';
import type { DirectoryResult } from 'tmp-promise';
import { dir } from 'tmp-promise';
import upath from 'upath';
import { logger } from '../../../../logger';
feat(config): allow exporting async config (#13075) * feat(config): allow exporting async config (#13035) module.exports can now be a function and it can be/return a Promise, allowing the results of asynchronous operations to be used in the configuration. The discussion leading up to this PR in #13035 assumed that module.exports had to be a plain object. But this commit: commit 9aa97af5b346f82b48564c20c66783b51c3022d9 Author: Nejc Habjan <hab.nejc@gmail.com> Date: Thu Dec 9 13:45:48 2021 +0100 feat(config)!: parse JSON5/YAML self-hosted admin config (#12644) Adds support for alternative admin config file formats. BREAKING CHANGE: Renovate will now fail if RENOVATE_CONFIG_FILE is specified without a file extension Had as an undocumented side effect, that it also handled transparenty if module.exports was assigned a Promise. With that commit, the promise will be await-ed so the resolved value is returned from getConfig(). That was not the case before that commit. So in this commit, configs that export functions are handled, and test cases for both promises and functions have been added. * Update lib/workers/global/config/parse/__fixtures__/fileAsyncFunction.js Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com> * Update lib/workers/global/config/parse/__fixtures__/fileFunctionPromise.js Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com> * feat(config): Fixed linter problems (#13035) * feat(config)!: Add doc for JSON5/YAML self-hosted admin config (#12644) The code was introduced in 9aa97af5b and here is the documentation to go with it * feat(config): Document config.js exports (#13035) * feat(config): Rename file*.js to config*.js because they really are config (#13035) * Update docs/usage/getting-started/running.md Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com> * Update docs/usage/getting-started/running.md Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com> Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com> Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
2021-12-13 21:51:36 +00:00
import customConfig from './__fixtures__/config';
2020-05-01 16:03:48 +00:00
import * as file from './file';
describe('workers/global/config/parse/file', () => {
const processExitSpy = jest.spyOn(process, 'exit');
const fsPathExistsSpy = jest.spyOn(fsExtra, 'pathExists');
const fsRemoveSpy = jest.spyOn(fsExtra, 'remove');
2021-03-02 16:16:05 +00:00
let tmp: DirectoryResult;
beforeAll(async () => {
tmp = await dir({ unsafeCleanup: true });
});
afterAll(async () => {
await tmp.cleanup();
});
describe('.getConfig()', () => {
it.each([
feat(config): allow exporting async config (#13075) * feat(config): allow exporting async config (#13035) module.exports can now be a function and it can be/return a Promise, allowing the results of asynchronous operations to be used in the configuration. The discussion leading up to this PR in #13035 assumed that module.exports had to be a plain object. But this commit: commit 9aa97af5b346f82b48564c20c66783b51c3022d9 Author: Nejc Habjan <hab.nejc@gmail.com> Date: Thu Dec 9 13:45:48 2021 +0100 feat(config)!: parse JSON5/YAML self-hosted admin config (#12644) Adds support for alternative admin config file formats. BREAKING CHANGE: Renovate will now fail if RENOVATE_CONFIG_FILE is specified without a file extension Had as an undocumented side effect, that it also handled transparenty if module.exports was assigned a Promise. With that commit, the promise will be await-ed so the resolved value is returned from getConfig(). That was not the case before that commit. So in this commit, configs that export functions are handled, and test cases for both promises and functions have been added. * Update lib/workers/global/config/parse/__fixtures__/fileAsyncFunction.js Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com> * Update lib/workers/global/config/parse/__fixtures__/fileFunctionPromise.js Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com> * feat(config): Fixed linter problems (#13035) * feat(config)!: Add doc for JSON5/YAML self-hosted admin config (#12644) The code was introduced in 9aa97af5b and here is the documentation to go with it * feat(config): Document config.js exports (#13035) * feat(config): Rename file*.js to config*.js because they really are config (#13035) * Update docs/usage/getting-started/running.md Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com> * Update docs/usage/getting-started/running.md Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com> Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com> Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
2021-12-13 21:51:36 +00:00
['custom js config file', 'config.js'],
['custom js config file', 'config.cjs'],
feat(config): allow exporting async config (#13075) * feat(config): allow exporting async config (#13035) module.exports can now be a function and it can be/return a Promise, allowing the results of asynchronous operations to be used in the configuration. The discussion leading up to this PR in #13035 assumed that module.exports had to be a plain object. But this commit: commit 9aa97af5b346f82b48564c20c66783b51c3022d9 Author: Nejc Habjan <hab.nejc@gmail.com> Date: Thu Dec 9 13:45:48 2021 +0100 feat(config)!: parse JSON5/YAML self-hosted admin config (#12644) Adds support for alternative admin config file formats. BREAKING CHANGE: Renovate will now fail if RENOVATE_CONFIG_FILE is specified without a file extension Had as an undocumented side effect, that it also handled transparenty if module.exports was assigned a Promise. With that commit, the promise will be await-ed so the resolved value is returned from getConfig(). That was not the case before that commit. So in this commit, configs that export functions are handled, and test cases for both promises and functions have been added. * Update lib/workers/global/config/parse/__fixtures__/fileAsyncFunction.js Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com> * Update lib/workers/global/config/parse/__fixtures__/fileFunctionPromise.js Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com> * feat(config): Fixed linter problems (#13035) * feat(config)!: Add doc for JSON5/YAML self-hosted admin config (#12644) The code was introduced in 9aa97af5b and here is the documentation to go with it * feat(config): Document config.js exports (#13035) * feat(config): Rename file*.js to config*.js because they really are config (#13035) * Update docs/usage/getting-started/running.md Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com> * Update docs/usage/getting-started/running.md Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com> Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com> Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
2021-12-13 21:51:36 +00:00
['custom js config file exporting a Promise', 'config-promise.js'],
['custom js config file exporting a function', 'config-function.js'],
// The next two are different syntactic ways of expressing the same thing
[
'custom js config file exporting a function returning a Promise',
'config-function-promise.js',
],
[
'custom js config file exporting an async function',
'config-async-function.js',
],
['.renovaterc', '.renovaterc'],
['JSON5 config file', 'config.json5'],
['YAML config file', 'config.yaml'],
])('parses %s', async (_fileType, filePath) => {
const configFile = upath.resolve(__dirname, './__fixtures__/', filePath);
expect(
await file.getConfig({ RENOVATE_CONFIG_FILE: configFile }),
).toEqual(customConfig);
2018-11-16 10:47:44 +00:00
});
it('migrates', async () => {
feat(config): allow exporting async config (#13075) * feat(config): allow exporting async config (#13035) module.exports can now be a function and it can be/return a Promise, allowing the results of asynchronous operations to be used in the configuration. The discussion leading up to this PR in #13035 assumed that module.exports had to be a plain object. But this commit: commit 9aa97af5b346f82b48564c20c66783b51c3022d9 Author: Nejc Habjan <hab.nejc@gmail.com> Date: Thu Dec 9 13:45:48 2021 +0100 feat(config)!: parse JSON5/YAML self-hosted admin config (#12644) Adds support for alternative admin config file formats. BREAKING CHANGE: Renovate will now fail if RENOVATE_CONFIG_FILE is specified without a file extension Had as an undocumented side effect, that it also handled transparenty if module.exports was assigned a Promise. With that commit, the promise will be await-ed so the resolved value is returned from getConfig(). That was not the case before that commit. So in this commit, configs that export functions are handled, and test cases for both promises and functions have been added. * Update lib/workers/global/config/parse/__fixtures__/fileAsyncFunction.js Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com> * Update lib/workers/global/config/parse/__fixtures__/fileFunctionPromise.js Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com> * feat(config): Fixed linter problems (#13035) * feat(config)!: Add doc for JSON5/YAML self-hosted admin config (#12644) The code was introduced in 9aa97af5b and here is the documentation to go with it * feat(config): Document config.js exports (#13035) * feat(config): Rename file*.js to config*.js because they really are config (#13035) * Update docs/usage/getting-started/running.md Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com> * Update docs/usage/getting-started/running.md Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com> Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com> Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
2021-12-13 21:51:36 +00:00
const configFile = upath.resolve(__dirname, './__fixtures__/config2.js');
const res = await file.getConfig({ RENOVATE_CONFIG_FILE: configFile });
2018-11-16 10:47:44 +00:00
expect(res).toMatchSnapshot();
2021-11-08 12:16:58 +00:00
expect(res.rangeStrategy).toBe('bump');
});
it('warns if config is invalid', async () => {
const configFile = upath.resolve(tmp.path, 'config.js');
const fileContent = `module.exports = {
"enabled": "invalid-value",
"prTitle":"something",
};`;
fs.writeFileSync(configFile, fileContent, { encoding: 'utf8' });
await file.getConfig({ RENOVATE_CONFIG_FILE: configFile });
expect(logger.warn).toHaveBeenCalledTimes(2);
fs.unlinkSync(configFile);
});
it('parse and returns empty config if there is no RENOVATE_CONFIG_FILE in env', async () => {
expect(await file.getConfig({})).toBeDefined();
});
it.each([
[
'config.js',
`module.exports = {
"platform": "github",
"token":"abcdef",
"onboarding": false,
2020-07-22 18:11:22 +00:00
"gitAuthor": "Renovate Bot <renovate@whitesourcesoftware.com>"
"onboardingConfig": {
"extends": ["config:recommended"],
},
"repositories": [ "test/test" ],
};`,
],
['config.json5', `"invalid":`],
['config.yaml', `invalid: -`],
])(
'fatal error and exit if error in parsing %s',
async (fileName, fileContent) => {
processExitSpy.mockImplementationOnce(() => undefined as never);
const configFile = upath.resolve(tmp.path, fileName);
fs.writeFileSync(configFile, fileContent, { encoding: 'utf8' });
await file.getConfig({ RENOVATE_CONFIG_FILE: configFile });
expect(processExitSpy).toHaveBeenCalledWith(1);
fs.unlinkSync(configFile);
},
);
it('fatal error and exit if custom config file does not exist', async () => {
processExitSpy
.mockImplementationOnce(() => undefined as never)
.mockImplementationOnce(() => undefined as never);
const configFile = upath.resolve(tmp.path, './file4.js');
await file.getConfig({ RENOVATE_CONFIG_FILE: configFile });
expect(processExitSpy).toHaveBeenCalledWith(1);
});
it('fatal error and exit if config.js contains unresolved env var', async () => {
processExitSpy.mockImplementationOnce(() => undefined as never);
const configFile = upath.resolve(
__dirname,
'./__fixtures__/config-ref-error.js-invalid',
);
const tmpDir = tmp.path;
await fsExtra.ensureDir(tmpDir);
const tmpConfigFile = upath.resolve(tmpDir, 'config-ref-error.js');
await fsExtra.copy(configFile, tmpConfigFile);
await file.getConfig({ RENOVATE_CONFIG_FILE: tmpConfigFile });
expect(logger.fatal).toHaveBeenCalledWith(
`Error parsing config file due to unresolved variable(s): CI_API_V4_URL is not defined`,
);
expect(processExitSpy).toHaveBeenCalledWith(1);
});
it.each([
['invalid config file type', './file.txt'],
['missing config file type', './file'],
])('fatal error and exit if %s', async (fileType, filePath) => {
processExitSpy.mockImplementationOnce(() => undefined as never);
const configFile = upath.resolve(tmp.path, filePath);
fs.writeFileSync(configFile, `{"token": "abc"}`, { encoding: 'utf8' });
await file.getConfig({ RENOVATE_CONFIG_FILE: configFile });
expect(processExitSpy).toHaveBeenCalledWith(1);
expect(logger.fatal).toHaveBeenCalledWith('Unsupported file type');
fs.unlinkSync(configFile);
});
});
describe('deleteConfigFile()', () => {
it.each([[undefined], [' ']])(
'skip when RENOVATE_CONFIG_FILE is not set ("%s")',
async (configFile) => {
await file.deleteNonDefaultConfig(
{ RENOVATE_CONFIG_FILE: configFile },
true,
);
expect(fsRemoveSpy).toHaveBeenCalledTimes(0);
},
);
it('skip when config file does not exist', async () => {
fsPathExistsSpy.mockResolvedValueOnce(false as never);
await file.deleteNonDefaultConfig(
{
RENOVATE_CONFIG_FILE: 'path',
},
true,
);
expect(fsRemoveSpy).toHaveBeenCalledTimes(0);
});
it.each([['false'], [' ']])(
'skip if deleteConfigFile is not set ("%s")',
async (deleteConfig) => {
fsPathExistsSpy.mockResolvedValueOnce(true as never);
await file.deleteNonDefaultConfig(
{
RENOVATE_CONFIG_FILE: '/path/to/config.js',
},
deleteConfig === 'true',
);
expect(fsRemoveSpy).toHaveBeenCalledTimes(0);
},
);
it('removes the specified config file', async () => {
fsRemoveSpy.mockImplementationOnce(() => {
// no-op
});
fsPathExistsSpy.mockResolvedValueOnce(true as never);
const configFile = '/path/to/config.js';
await file.deleteNonDefaultConfig(
{
RENOVATE_CONFIG_FILE: configFile,
},
true,
);
expect(fsRemoveSpy).toHaveBeenCalledTimes(1);
expect(fsRemoveSpy).toHaveBeenCalledWith(configFile);
expect(logger.trace).toHaveBeenCalledWith(
expect.anything(),
'config file successfully deleted',
);
});
it('fails silently when attempting to delete the config file', async () => {
fsRemoveSpy.mockImplementationOnce(() => {
throw new Error();
});
fsPathExistsSpy.mockResolvedValueOnce(true as never);
const configFile = '/path/to/config.js';
await file.deleteNonDefaultConfig(
{
RENOVATE_CONFIG_FILE: configFile,
},
true,
);
expect(fsRemoveSpy).toHaveBeenCalledTimes(1);
expect(fsRemoveSpy).toHaveBeenCalledWith(configFile);
expect(logger.warn).toHaveBeenCalledWith(
expect.anything(),
'error deleting config file',
);
});
});
});