mirror of
https://github.com/renovatebot/renovate.git
synced 2026-10-25 15:01:49 +00:00
8b3a066756
As we're now managing them via a `afterEach` in `test/setup.ts`, we can remove any entries in tests themselves. We don't update overrides of an outer hook or cases where there are more explicit `clear()`s. Co-authored-by: Claude Sonnet 5 <jamie.tanna+claude-code@mend.io>
452 lines
13 KiB
TypeScript
452 lines
13 KiB
TypeScript
import { fs } from '~test/util.ts';
|
|
import * as hostRules from '../../util/host-rules.ts';
|
|
import { GitRefsDatasource } from '../datasource/git-refs/index.ts';
|
|
import { GitTagsDatasource } from '../datasource/git-tags/index.ts';
|
|
import { GithubTagsDatasource } from '../datasource/github-tags/index.ts';
|
|
import { GitlabTagsDatasource } from '../datasource/gitlab-tags/index.ts';
|
|
import { type PackageDependency } from './types.ts';
|
|
import {
|
|
applyGitSource,
|
|
artifactError,
|
|
artifactErrorMessageFromExecError,
|
|
artifactErrorResult,
|
|
fileAddition,
|
|
fileChangesToArtifactResults,
|
|
resolveToolConstraint,
|
|
updateLockFile,
|
|
} from './util.ts';
|
|
|
|
vi.mock('../../util/fs/index.ts');
|
|
|
|
describe('modules/manager/util', () => {
|
|
it('applies GitHub source for tag', () => {
|
|
const dependency: PackageDependency = {};
|
|
const git = 'https://github.com/foo/bar';
|
|
const tag = 'v1.2.3';
|
|
|
|
applyGitSource(dependency, git, undefined, tag, undefined);
|
|
|
|
expect(dependency).toStrictEqual({
|
|
datasource: GithubTagsDatasource.id,
|
|
registryUrls: ['https://github.com'],
|
|
packageName: 'foo/bar',
|
|
currentValue: tag,
|
|
skipReason: undefined,
|
|
});
|
|
});
|
|
|
|
it('applies GitLab source for tag', () => {
|
|
const dependency: PackageDependency = {};
|
|
const git = 'https://gitlab.com/foo/bar';
|
|
const tag = 'v1.2.3';
|
|
|
|
applyGitSource(dependency, git, undefined, tag, undefined);
|
|
|
|
expect(dependency).toStrictEqual({
|
|
datasource: GitlabTagsDatasource.id,
|
|
registryUrls: ['https://gitlab.com'],
|
|
packageName: 'foo/bar',
|
|
currentValue: tag,
|
|
skipReason: undefined,
|
|
});
|
|
});
|
|
|
|
it('applies other git source for tag', () => {
|
|
const dependency: PackageDependency = {};
|
|
const git = 'https://a-git-source.com/foo/bar';
|
|
const tag = 'v1.2.3';
|
|
|
|
applyGitSource(dependency, git, undefined, tag, undefined);
|
|
|
|
expect(dependency).toStrictEqual({
|
|
datasource: GitTagsDatasource.id,
|
|
packageName: git,
|
|
currentValue: tag,
|
|
skipReason: undefined,
|
|
});
|
|
});
|
|
|
|
it('applies git source with subdomain', () => {
|
|
const dependency: PackageDependency = {};
|
|
const git = 'https://git.example.com/foo/bar';
|
|
const tag = 'v1.2.3';
|
|
|
|
hostRules.add({
|
|
hostType: 'github',
|
|
matchHost: 'git.example.com',
|
|
});
|
|
applyGitSource(dependency, git, undefined, tag, undefined);
|
|
|
|
expect(dependency).toStrictEqual({
|
|
datasource: GithubTagsDatasource.id,
|
|
packageName: 'foo/bar',
|
|
currentValue: tag,
|
|
registryUrls: ['https://git.example.com'],
|
|
skipReason: undefined,
|
|
});
|
|
});
|
|
|
|
it('applies GitHub source for tag with SSH URL', () => {
|
|
const dependency: PackageDependency = {};
|
|
const git = 'ssh://git@github.com/foo/bar';
|
|
const tag = 'v1.2.3';
|
|
|
|
applyGitSource(dependency, git, undefined, tag, undefined);
|
|
|
|
expect(dependency).toStrictEqual({
|
|
datasource: GithubTagsDatasource.id,
|
|
registryUrls: ['https://github.com'],
|
|
packageName: 'foo/bar',
|
|
currentValue: tag,
|
|
skipReason: undefined,
|
|
});
|
|
});
|
|
|
|
it('applies GitLab source for tag with SSH URL', () => {
|
|
const dependency: PackageDependency = {};
|
|
const git = 'ssh://git@gitlab.com/foo/bar';
|
|
const tag = 'v1.2.3';
|
|
|
|
applyGitSource(dependency, git, undefined, tag, undefined);
|
|
|
|
expect(dependency).toStrictEqual({
|
|
datasource: GitlabTagsDatasource.id,
|
|
registryUrls: ['https://gitlab.com'],
|
|
packageName: 'foo/bar',
|
|
currentValue: tag,
|
|
skipReason: undefined,
|
|
});
|
|
});
|
|
|
|
it('applies GitHub source for tag with HTTPS URL', () => {
|
|
const dependency: PackageDependency = {};
|
|
const git = 'https://github.com/foo/bar';
|
|
const tag = 'v1.2.3';
|
|
|
|
applyGitSource(dependency, git, undefined, tag, undefined);
|
|
|
|
expect(dependency).toStrictEqual({
|
|
datasource: GithubTagsDatasource.id,
|
|
registryUrls: ['https://github.com'],
|
|
packageName: 'foo/bar',
|
|
currentValue: tag,
|
|
skipReason: undefined,
|
|
});
|
|
});
|
|
|
|
it('applies git source for rev', () => {
|
|
const dependency: PackageDependency = {};
|
|
const git = 'https://github.com/foo/bar';
|
|
const rev = 'abc1234';
|
|
|
|
applyGitSource(dependency, git, rev, undefined, undefined);
|
|
|
|
expect(dependency).toStrictEqual({
|
|
datasource: GitRefsDatasource.id,
|
|
packageName: git,
|
|
currentDigest: rev,
|
|
replaceString: rev,
|
|
skipReason: undefined,
|
|
});
|
|
});
|
|
|
|
it('skips git source for branch', () => {
|
|
const dependency: PackageDependency = {};
|
|
const git = 'https://github.com/foo/bar';
|
|
const branch = 'main';
|
|
|
|
applyGitSource(dependency, git, undefined, undefined, branch);
|
|
|
|
expect(dependency).toStrictEqual({
|
|
datasource: GitRefsDatasource.id,
|
|
packageName: git,
|
|
currentValue: branch,
|
|
skipReason: 'git-dependency',
|
|
});
|
|
});
|
|
|
|
it('skips git source for git only', () => {
|
|
const dependency: PackageDependency = {};
|
|
const git = 'https://github.com/foo/bar';
|
|
|
|
applyGitSource(dependency, git, undefined, undefined, undefined);
|
|
|
|
expect(dependency).toStrictEqual({
|
|
datasource: GitRefsDatasource.id,
|
|
packageName: git,
|
|
currentValue: undefined,
|
|
skipReason: 'unspecified-version',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('modules/manager/util', () => {
|
|
it('returns stderr when present', () => {
|
|
const message = artifactErrorMessageFromExecError(
|
|
{ stderr: 'some error', stdout: 'some output' },
|
|
'fallback message',
|
|
);
|
|
|
|
expect(message).toBe('some error');
|
|
});
|
|
|
|
it('returns stdout when stderr is empty', () => {
|
|
const message = artifactErrorMessageFromExecError(
|
|
{ stderr: '', stdout: 'some output' },
|
|
'fallback message',
|
|
);
|
|
|
|
expect(message).toBe('some output');
|
|
});
|
|
|
|
it('returns stdout when stderr is only whitespace', () => {
|
|
const message = artifactErrorMessageFromExecError(
|
|
{ stderr: ' ', stdout: 'some output' },
|
|
'fallback message',
|
|
);
|
|
|
|
expect(message).toBe('some output');
|
|
});
|
|
|
|
it('returns stdout when stderr is undefined', () => {
|
|
const message = artifactErrorMessageFromExecError(
|
|
{ stdout: 'some output' },
|
|
'fallback message',
|
|
);
|
|
|
|
expect(message).toBe('some output');
|
|
});
|
|
|
|
it('returns fallback message when neither stderr nor stdout are present', () => {
|
|
const message = artifactErrorMessageFromExecError({}, 'fallback message');
|
|
|
|
expect(message).toBe('fallback message');
|
|
});
|
|
|
|
it('returns fallback message when stderr and stdout are only whitespace', () => {
|
|
const message = artifactErrorMessageFromExecError(
|
|
{ stderr: ' ', stdout: ' ' },
|
|
'fallback message',
|
|
);
|
|
|
|
expect(message).toBe('fallback message');
|
|
});
|
|
|
|
it('wraps file changes into artifact results', () => {
|
|
expect(
|
|
fileChangesToArtifactResults([
|
|
{ type: 'addition', path: 'foo', contents: 'bar' },
|
|
{ type: 'deletion', path: 'baz' },
|
|
]),
|
|
).toEqual([
|
|
{ file: { type: 'addition', path: 'foo', contents: 'bar' } },
|
|
{ file: { type: 'deletion', path: 'baz' } },
|
|
]);
|
|
});
|
|
|
|
describe('resolveToolConstraint()', () => {
|
|
it('prefers the user configured constraint', async () => {
|
|
const constraint = await resolveToolConstraint(
|
|
{
|
|
constraints: { python: '==3.12' },
|
|
extractedConstraints: { python: '==3.10' },
|
|
},
|
|
'python',
|
|
() => '==3.11',
|
|
);
|
|
|
|
expect(constraint).toBe('==3.12');
|
|
});
|
|
|
|
it('prefers the derived constraint over the extracted one', async () => {
|
|
const constraint = await resolveToolConstraint(
|
|
{ extractedConstraints: { python: '==3.10' } },
|
|
'python',
|
|
() => Promise.resolve('==3.11'),
|
|
);
|
|
|
|
expect(constraint).toBe('==3.11');
|
|
});
|
|
|
|
it('falls back to the extracted constraint', async () => {
|
|
const constraint = await resolveToolConstraint(
|
|
{ extractedConstraints: { python: '==3.10' } },
|
|
'python',
|
|
() => null,
|
|
);
|
|
|
|
expect(constraint).toBe('==3.10');
|
|
});
|
|
|
|
it('returns the extracted constraint when nothing can be derived', async () => {
|
|
const constraint = await resolveToolConstraint(
|
|
{ extractedConstraints: { python: '==3.10' } },
|
|
'python',
|
|
);
|
|
|
|
expect(constraint).toBe('==3.10');
|
|
});
|
|
|
|
it('returns undefined when no constraint is known', async () => {
|
|
const constraint = await resolveToolConstraint({}, 'python', () => null);
|
|
|
|
expect(constraint).toBeUndefined();
|
|
});
|
|
|
|
it('treats an empty string as not set', async () => {
|
|
const constraint = await resolveToolConstraint(
|
|
{
|
|
constraints: { python: '' },
|
|
extractedConstraints: { python: '==3.10' },
|
|
},
|
|
'python',
|
|
() => '',
|
|
);
|
|
|
|
expect(constraint).toBe('==3.10');
|
|
});
|
|
|
|
it('accepts the null constraints of a post-update config', async () => {
|
|
const constraint = await resolveToolConstraint(
|
|
{ constraints: null, extractedConstraints: null },
|
|
'node',
|
|
);
|
|
|
|
expect(constraint).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
it('builds an addition result', () => {
|
|
expect(fileAddition('foo.lock', 'new')).toEqual({
|
|
file: { type: 'addition', path: 'foo.lock', contents: 'new' },
|
|
});
|
|
});
|
|
|
|
it('builds an artifact error result', () => {
|
|
expect(artifactError('foo.lock', 'boom')).toEqual({
|
|
artifactError: { fileName: 'foo.lock', stderr: 'boom' },
|
|
});
|
|
});
|
|
|
|
it('builds an artifact error result from an error', () => {
|
|
expect(
|
|
artifactErrorResult('foo.lock', Object.assign(new Error('msg'), {})),
|
|
).toEqual([{ artifactError: { fileName: 'foo.lock', stderr: 'msg' } }]);
|
|
|
|
expect(
|
|
artifactErrorResult(
|
|
'foo.lock',
|
|
Object.assign(new Error('msg'), { stderr: 'from stderr' }),
|
|
),
|
|
).toEqual([
|
|
{ artifactError: { fileName: 'foo.lock', stderr: 'from stderr' } },
|
|
]);
|
|
});
|
|
|
|
describe('updateLockFile', () => {
|
|
it('writes the package file, deletes the lock file and returns the update', async () => {
|
|
fs.readLocalFile.mockResolvedValueOnce('new content');
|
|
const run = vi.fn().mockResolvedValue(undefined);
|
|
|
|
const res = await updateLockFile({
|
|
lockFileName: 'foo.lock',
|
|
existingLockFileContent: 'old content',
|
|
packageFile: { path: 'foo.json', contents: 'new package file' },
|
|
deleteLockFile: true,
|
|
run,
|
|
});
|
|
|
|
expect(res).toEqual([
|
|
{
|
|
file: {
|
|
type: 'addition',
|
|
path: 'foo.lock',
|
|
contents: 'new content',
|
|
},
|
|
},
|
|
]);
|
|
expect(fs.writeLocalFile).toHaveBeenCalledWith(
|
|
'foo.json',
|
|
'new package file',
|
|
);
|
|
expect(fs.deleteLocalFile).toHaveBeenCalledWith('foo.lock');
|
|
expect(fs.readLocalFile).toHaveBeenCalledWith('foo.lock', 'utf8');
|
|
expect(run).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it('keeps the package and lock file when not asked to touch them', async () => {
|
|
fs.readLocalFile.mockResolvedValueOnce('new content');
|
|
|
|
await updateLockFile({
|
|
lockFileName: 'foo.lock',
|
|
existingLockFileContent: 'old content',
|
|
run: () => Promise.resolve(),
|
|
});
|
|
|
|
expect(fs.writeLocalFile).not.toHaveBeenCalled();
|
|
expect(fs.deleteLocalFile).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('returns null when the lock file is gone', async () => {
|
|
fs.readLocalFile.mockResolvedValueOnce(null);
|
|
|
|
await expect(
|
|
updateLockFile({
|
|
lockFileName: 'foo.lock',
|
|
existingLockFileContent: 'old content',
|
|
run: () => Promise.resolve(),
|
|
}),
|
|
).resolves.toBeNull();
|
|
});
|
|
|
|
it('returns null when the lock file is unchanged', async () => {
|
|
fs.readLocalFile.mockResolvedValueOnce('old content');
|
|
|
|
await expect(
|
|
updateLockFile({
|
|
lockFileName: 'foo.lock',
|
|
existingLockFileContent: 'old content',
|
|
run: () => Promise.resolve(),
|
|
}),
|
|
).resolves.toBeNull();
|
|
});
|
|
|
|
it('reads and returns the lock file as bytes when given a Buffer', async () => {
|
|
fs.readLocalFile.mockResolvedValueOnce(
|
|
Buffer.from('new content') as never,
|
|
);
|
|
|
|
const res = await updateLockFile({
|
|
lockFileName: 'foo.lockb',
|
|
existingLockFileContent: Buffer.from('old content'),
|
|
run: () => Promise.resolve(),
|
|
});
|
|
|
|
expect(res).toEqual([
|
|
{
|
|
file: {
|
|
type: 'addition',
|
|
path: 'foo.lockb',
|
|
contents: Buffer.from('new content'),
|
|
},
|
|
},
|
|
]);
|
|
expect(fs.readLocalFile).toHaveBeenCalledWith('foo.lockb');
|
|
});
|
|
|
|
it('returns null when a binary lock file is unchanged', async () => {
|
|
fs.readLocalFile.mockResolvedValueOnce(
|
|
Buffer.from('old content') as never,
|
|
);
|
|
|
|
await expect(
|
|
updateLockFile({
|
|
lockFileName: 'foo.lockb',
|
|
existingLockFileContent: Buffer.from('old content'),
|
|
run: () => Promise.resolve(),
|
|
}),
|
|
).resolves.toBeNull();
|
|
});
|
|
});
|
|
});
|