mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-12 15:06:27 +00:00
757 lines
19 KiB
TypeScript
757 lines
19 KiB
TypeScript
import type { PackageRuleInputConfig, UpdateType } from '../config/types';
|
|
import { ProgrammingLanguage } from '../constants';
|
|
|
|
import * as datasourceDocker from '../datasource/docker';
|
|
import { OrbDatasource } from '../datasource/orb';
|
|
import { applyPackageRules } from './package-rules';
|
|
|
|
type TestConfig = PackageRuleInputConfig & {
|
|
x?: number;
|
|
y?: number;
|
|
groupName?: string;
|
|
};
|
|
|
|
describe('util/package-rules', () => {
|
|
const config1: TestConfig = {
|
|
foo: 'bar',
|
|
|
|
packageRules: [
|
|
{
|
|
matchPackageNames: ['a', 'b'],
|
|
matchPackagePrefixes: ['xyz/'],
|
|
excludePackagePrefixes: ['xyz/foo'],
|
|
x: 2,
|
|
},
|
|
{
|
|
matchPackagePatterns: ['a', 'b'],
|
|
excludePackageNames: ['aa'],
|
|
excludePackagePatterns: ['d'],
|
|
y: 2,
|
|
},
|
|
{
|
|
matchPackagePrefixes: ['xyz/'],
|
|
excludePackageNames: ['xyz/foo'],
|
|
groupName: 'xyz',
|
|
},
|
|
],
|
|
};
|
|
it('applies', () => {
|
|
const config: PackageRuleInputConfig = {
|
|
depName: 'a',
|
|
isBump: true,
|
|
currentValue: '1.0.0',
|
|
packageRules: [
|
|
{
|
|
matchPackagePatterns: ['*'],
|
|
matchCurrentVersion: '<= 2.0.0',
|
|
},
|
|
{
|
|
matchPackageNames: ['b'],
|
|
matchCurrentVersion: '<= 2.0.0',
|
|
},
|
|
{
|
|
excludePackagePatterns: ['*'],
|
|
matchPackageNames: ['b'],
|
|
},
|
|
{
|
|
matchUpdateTypes: ['bump'],
|
|
},
|
|
{
|
|
excludePackageNames: ['a'],
|
|
matchPackageNames: ['b'],
|
|
},
|
|
{
|
|
matchCurrentVersion: '<= 2.0.0',
|
|
},
|
|
],
|
|
};
|
|
// FIXME: explicit assert condition
|
|
expect(applyPackageRules(config)).toMatchSnapshot();
|
|
});
|
|
it('applies both rules for a', () => {
|
|
const dep = {
|
|
depName: 'a',
|
|
};
|
|
const res = applyPackageRules({ ...config1, ...dep });
|
|
expect(res.x).toBe(2);
|
|
expect(res.y).toBe(2);
|
|
expect(res.groupName).toBeUndefined();
|
|
});
|
|
it('applies both rules for b', () => {
|
|
const dep = {
|
|
depName: 'b',
|
|
};
|
|
const res = applyPackageRules({ ...config1, ...dep });
|
|
expect(res.x).toBe(2);
|
|
expect(res.y).toBe(2);
|
|
expect(res.groupName).toBeUndefined();
|
|
});
|
|
it('applies the second rule', () => {
|
|
const dep = {
|
|
depName: 'abc',
|
|
};
|
|
const res = applyPackageRules({ ...config1, ...dep });
|
|
expect(res.x).toBeUndefined();
|
|
expect(res.y).toBe(2);
|
|
expect(res.groupName).toBeUndefined();
|
|
});
|
|
it('applies matchPackagePrefixes', () => {
|
|
const dep = {
|
|
depName: 'xyz/abc',
|
|
};
|
|
const res = applyPackageRules({ ...config1, ...dep });
|
|
expect(res.x).toBe(2);
|
|
expect(res.y).toBe(2);
|
|
expect(res.groupName).toBe('xyz');
|
|
});
|
|
|
|
it('applies excludePackageNames', () => {
|
|
const dep = {
|
|
depName: 'xyz/foo',
|
|
};
|
|
const res = applyPackageRules({ ...config1, ...dep });
|
|
expect(res.groupName).toBeUndefined();
|
|
});
|
|
|
|
it('applies excludePackagePrefixes', () => {
|
|
const dep = {
|
|
depName: 'xyz/foo-a',
|
|
};
|
|
const res = applyPackageRules({ ...config1, ...dep });
|
|
expect(res.x).toBeUndefined();
|
|
expect(res.groupName).toBe('xyz');
|
|
});
|
|
it('applies the second second rule', () => {
|
|
const dep = {
|
|
depName: 'bc',
|
|
};
|
|
const res = applyPackageRules({ ...config1, ...dep });
|
|
expect(res.x).toBeUndefined();
|
|
expect(res.y).toBe(2);
|
|
});
|
|
it('excludes package name', () => {
|
|
const dep = {
|
|
depName: 'aa',
|
|
};
|
|
const res = applyPackageRules({ ...config1, ...dep });
|
|
expect(res.x).toBeUndefined();
|
|
expect(res.y).toBeUndefined();
|
|
});
|
|
it('excludes package pattern', () => {
|
|
const dep = {
|
|
depName: 'bcd',
|
|
};
|
|
const res = applyPackageRules({ ...config1, ...dep });
|
|
expect(res.x).toBeUndefined();
|
|
expect(res.y).toBeUndefined();
|
|
});
|
|
it('ignores patterns if lock file maintenance', () => {
|
|
const dep = {
|
|
enabled: true,
|
|
matchPackagePatterns: ['.*'],
|
|
updateType: 'lockFileMaintenance' as UpdateType,
|
|
packageRules: [
|
|
{
|
|
excludePackagePatterns: ['^foo'],
|
|
enabled: false,
|
|
},
|
|
],
|
|
};
|
|
const res = applyPackageRules(dep);
|
|
expect(res.enabled).toBeTrue();
|
|
const res2 = applyPackageRules({ ...dep, depName: 'anything' });
|
|
expect(res2.enabled).toBeFalse();
|
|
});
|
|
it('matches anything if missing inclusive rules', () => {
|
|
const config: TestConfig = {
|
|
packageRules: [
|
|
{
|
|
excludePackageNames: ['foo'],
|
|
x: 1,
|
|
},
|
|
],
|
|
};
|
|
const res1 = applyPackageRules({
|
|
...config,
|
|
depName: 'foo',
|
|
});
|
|
expect(res1.x).toBeUndefined();
|
|
const res2 = applyPackageRules({
|
|
...config,
|
|
depName: 'bar',
|
|
});
|
|
expect(res2.x).toBeDefined();
|
|
});
|
|
it('supports inclusive or', () => {
|
|
const config: TestConfig = {
|
|
packageRules: [
|
|
{
|
|
matchPackageNames: ['neutrino'],
|
|
matchPackagePatterns: ['^@neutrino\\/'],
|
|
x: 1,
|
|
},
|
|
],
|
|
};
|
|
const res1 = applyPackageRules({ ...config, depName: 'neutrino' });
|
|
expect(res1.x).toBeDefined();
|
|
const res2 = applyPackageRules({
|
|
...config,
|
|
depName: '@neutrino/something',
|
|
});
|
|
expect(res2.x).toBeDefined();
|
|
});
|
|
it('filters requested depType', () => {
|
|
const config: TestConfig = {
|
|
packageRules: [
|
|
{
|
|
matchDepTypes: ['dependencies', 'peerDependencies'],
|
|
matchPackageNames: ['a'],
|
|
x: 1,
|
|
},
|
|
],
|
|
};
|
|
const dep = {
|
|
depType: 'dependencies',
|
|
depName: 'a',
|
|
};
|
|
const res = applyPackageRules({ ...config, ...dep });
|
|
expect(res.x).toBe(1);
|
|
});
|
|
it('filters from list of requested depTypes', () => {
|
|
const config: TestConfig = {
|
|
packageRules: [
|
|
{
|
|
matchDepTypes: ['test'],
|
|
matchPackageNames: ['a'],
|
|
x: 1,
|
|
},
|
|
],
|
|
};
|
|
const dep = {
|
|
depTypes: ['build', 'test'],
|
|
depName: 'a',
|
|
};
|
|
const res = applyPackageRules({ ...config, ...dep });
|
|
expect(res.x).toBe(1);
|
|
});
|
|
it('filters managers with matching manager', () => {
|
|
const config: TestConfig = {
|
|
packageRules: [
|
|
{
|
|
matchManagers: ['npm', 'meteor'],
|
|
matchPackageNames: ['node'],
|
|
x: 1,
|
|
},
|
|
],
|
|
};
|
|
const dep = {
|
|
depType: 'dependencies',
|
|
language: ProgrammingLanguage.JavaScript,
|
|
manager: 'meteor',
|
|
depName: 'node',
|
|
};
|
|
const res = applyPackageRules({ ...config, ...dep });
|
|
expect(res.x).toBe(1);
|
|
});
|
|
it('filters managers with non-matching manager', () => {
|
|
const config: TestConfig = {
|
|
packageRules: [
|
|
{
|
|
matchManagers: ['dockerfile', 'npm'],
|
|
matchPackageNames: ['node'],
|
|
x: 1,
|
|
},
|
|
],
|
|
};
|
|
const dep = {
|
|
depType: 'dependencies',
|
|
language: ProgrammingLanguage.Python,
|
|
manager: 'pipenv',
|
|
depName: 'node',
|
|
};
|
|
const res = applyPackageRules({ ...config, ...dep });
|
|
expect(res.x).toBeUndefined();
|
|
});
|
|
it('filters languages with matching language', () => {
|
|
const config: TestConfig = {
|
|
packageRules: [
|
|
{
|
|
matchLanguages: [
|
|
ProgrammingLanguage.JavaScript,
|
|
ProgrammingLanguage.NodeJS,
|
|
],
|
|
matchPackageNames: ['node'],
|
|
x: 1,
|
|
},
|
|
],
|
|
};
|
|
const dep = {
|
|
depType: 'dependencies',
|
|
language: ProgrammingLanguage.JavaScript,
|
|
manager: 'meteor',
|
|
depName: 'node',
|
|
};
|
|
const res = applyPackageRules({ ...config, ...dep });
|
|
expect(res.x).toBe(1);
|
|
});
|
|
it('filters languages with non-matching language', () => {
|
|
const config: TestConfig = {
|
|
packageRules: [
|
|
{
|
|
matchLanguages: [ProgrammingLanguage.Docker],
|
|
matchPackageNames: ['node'],
|
|
x: 1,
|
|
},
|
|
],
|
|
};
|
|
const dep = {
|
|
depType: 'dependencies',
|
|
language: ProgrammingLanguage.Python,
|
|
manager: 'pipenv',
|
|
depName: 'node',
|
|
};
|
|
const res = applyPackageRules({ ...config, ...dep });
|
|
expect(res.x).toBeUndefined();
|
|
});
|
|
it('filters datasources with matching datasource', () => {
|
|
const config: TestConfig = {
|
|
packageRules: [
|
|
{
|
|
matchDatasources: [OrbDatasource.id, datasourceDocker.id],
|
|
x: 1,
|
|
},
|
|
],
|
|
};
|
|
const dep = {
|
|
depType: 'dependencies',
|
|
datasource: OrbDatasource.id,
|
|
baseBranch: 'master',
|
|
};
|
|
const res = applyPackageRules({ ...config, ...dep });
|
|
expect(res.x).toBe(1);
|
|
});
|
|
it('filters branches with matching branch', () => {
|
|
const config: TestConfig = {
|
|
packageRules: [
|
|
{
|
|
matchBaseBranches: ['master', 'staging'],
|
|
x: 1,
|
|
},
|
|
],
|
|
};
|
|
const dep = {
|
|
depType: 'dependencies',
|
|
datasource: OrbDatasource.id,
|
|
baseBranch: 'master',
|
|
};
|
|
const res = applyPackageRules({ ...config, ...dep });
|
|
expect(res.x).toBe(1);
|
|
});
|
|
it('filters datasources with non-matching datasource', () => {
|
|
const config: TestConfig = {
|
|
packageRules: [
|
|
{
|
|
matchDatasources: [OrbDatasource.id],
|
|
x: 1,
|
|
},
|
|
],
|
|
};
|
|
const dep = {
|
|
depType: 'dependencies',
|
|
baseBranch: 'staging',
|
|
};
|
|
const res = applyPackageRules({ ...config, ...dep });
|
|
expect(res.x).toBeUndefined();
|
|
});
|
|
it('filters branches with non-matching branch', () => {
|
|
const config: TestConfig = {
|
|
packageRules: [
|
|
{
|
|
matchBaseBranches: ['master'],
|
|
x: 1,
|
|
},
|
|
],
|
|
};
|
|
const dep = {
|
|
depType: 'dependencies',
|
|
baseBranch: 'staging',
|
|
};
|
|
const res = applyPackageRules({ ...config, ...dep });
|
|
expect(res.x).toBeUndefined();
|
|
});
|
|
it('filters updateType', () => {
|
|
const config: TestConfig = {
|
|
packageRules: [
|
|
{
|
|
matchUpdateTypes: ['minor', 'patch'],
|
|
x: 1,
|
|
},
|
|
],
|
|
};
|
|
const dep = {
|
|
depType: 'dependencies',
|
|
depName: 'a',
|
|
updateType: 'patch' as UpdateType,
|
|
};
|
|
const res = applyPackageRules({ ...config, ...dep });
|
|
expect(res.x).toBe(1);
|
|
});
|
|
it('matches matchSourceUrlPrefixes', () => {
|
|
const config: TestConfig = {
|
|
packageRules: [
|
|
{
|
|
matchSourceUrlPrefixes: [
|
|
'https://github.com/foo/bar',
|
|
'https://github.com/renovatebot/',
|
|
],
|
|
x: 1,
|
|
},
|
|
],
|
|
};
|
|
const dep = {
|
|
depType: 'dependencies',
|
|
depName: 'a',
|
|
updateType: 'patch' as UpdateType,
|
|
sourceUrl: 'https://github.com/renovatebot/presets',
|
|
};
|
|
const res = applyPackageRules({ ...config, ...dep });
|
|
expect(res.x).toBe(1);
|
|
});
|
|
it('non-matches matchSourceUrlPrefixes', () => {
|
|
const config: TestConfig = {
|
|
packageRules: [
|
|
{
|
|
matchSourceUrlPrefixes: [
|
|
'https://github.com/foo/bar',
|
|
'https://github.com/renovatebot/',
|
|
],
|
|
x: 1,
|
|
},
|
|
],
|
|
};
|
|
const dep = {
|
|
depType: 'dependencies',
|
|
depName: 'a',
|
|
updateType: 'patch' as UpdateType,
|
|
sourceUrl: 'https://github.com/vuejs/vue',
|
|
};
|
|
const res = applyPackageRules({ ...config, ...dep });
|
|
expect(res.x).toBeUndefined();
|
|
});
|
|
it('handles matchSourceUrlPrefixes when missing sourceUrl', () => {
|
|
const config: TestConfig = {
|
|
packageRules: [
|
|
{
|
|
matchSourceUrlPrefixes: [
|
|
'https://github.com/foo/bar',
|
|
'https://github.com/renovatebot/',
|
|
],
|
|
x: 1,
|
|
},
|
|
],
|
|
};
|
|
const dep = {
|
|
depType: 'dependencies',
|
|
depName: 'a',
|
|
updateType: 'patch' as UpdateType,
|
|
};
|
|
const res = applyPackageRules({ ...config, ...dep });
|
|
expect(res.x).toBeUndefined();
|
|
});
|
|
it('filters naked depType', () => {
|
|
const config: TestConfig = {
|
|
packageRules: [
|
|
{
|
|
matchDepTypes: ['dependencies', 'peerDependencies'],
|
|
x: 1,
|
|
},
|
|
],
|
|
};
|
|
const dep = {
|
|
depType: 'dependencies',
|
|
depName: 'a',
|
|
};
|
|
const res = applyPackageRules({ ...config, ...dep });
|
|
expect(res.x).toBe(1);
|
|
});
|
|
it('filters out unrequested depType', () => {
|
|
const config: TestConfig = {
|
|
packageRules: [
|
|
{
|
|
matchDepTypes: ['dependencies', 'peerDependencies'],
|
|
matchPackageNames: ['a'],
|
|
x: 1,
|
|
},
|
|
],
|
|
};
|
|
const dep = {
|
|
depType: 'devDependencies',
|
|
depName: 'a',
|
|
};
|
|
const res = applyPackageRules({ ...config, ...dep });
|
|
expect(res.x).toBeUndefined();
|
|
});
|
|
it('checks if matchCurrentVersion selector is valid and satisfies the condition on range overlap', () => {
|
|
const config: TestConfig = {
|
|
packageRules: [
|
|
{
|
|
matchPackageNames: ['test'],
|
|
matchCurrentVersion: '<= 2.0.0',
|
|
x: 1,
|
|
},
|
|
],
|
|
};
|
|
const res1 = applyPackageRules({
|
|
...config,
|
|
...{
|
|
depName: 'test',
|
|
currentValue: '^1.0.0',
|
|
currentVersion: '1.0.3',
|
|
},
|
|
});
|
|
expect(res1.x).toBeDefined();
|
|
const res2 = applyPackageRules({
|
|
...config,
|
|
...{
|
|
depName: 'test',
|
|
currentValue: '^1.0.0',
|
|
},
|
|
});
|
|
expect(res2.x).toBeUndefined();
|
|
});
|
|
it('checks if matchCurrentVersion selector is valid and satisfies the condition on pinned to range overlap', () => {
|
|
const config: TestConfig = {
|
|
packageRules: [
|
|
{
|
|
matchPackageNames: ['test'],
|
|
matchCurrentVersion: '>= 2.0.0',
|
|
x: 1,
|
|
},
|
|
],
|
|
};
|
|
const res1 = applyPackageRules({
|
|
...config,
|
|
...{
|
|
depName: 'test',
|
|
currentValue: '2.4.6',
|
|
currentVersion: '2.4.6',
|
|
},
|
|
});
|
|
expect(res1.x).toBeDefined();
|
|
});
|
|
it('checks if matchCurrentVersion selector is a version and matches if currentValue is a range', () => {
|
|
const config: TestConfig = {
|
|
packageRules: [
|
|
{
|
|
matchPackageNames: ['test'],
|
|
matchCurrentVersion: '2.1.0',
|
|
x: 1,
|
|
},
|
|
],
|
|
};
|
|
const res1 = applyPackageRules({
|
|
...config,
|
|
...{
|
|
depName: 'test',
|
|
currentValue: '^2.0.0',
|
|
},
|
|
});
|
|
expect(res1.x).toBeDefined();
|
|
const res2 = applyPackageRules({
|
|
...config,
|
|
...{
|
|
depName: 'test',
|
|
currentValue: '~2.0.0',
|
|
},
|
|
});
|
|
expect(res2.x).toBeUndefined();
|
|
});
|
|
it('checks if matchCurrentVersion selector works with static values', () => {
|
|
const config: TestConfig = {
|
|
packageRules: [
|
|
{
|
|
matchPackageNames: ['test'],
|
|
matchCurrentVersion: '4.6.0',
|
|
x: 1,
|
|
},
|
|
],
|
|
};
|
|
const res1 = applyPackageRules({
|
|
...config,
|
|
...{
|
|
depName: 'test',
|
|
currentValue: '4.6.0',
|
|
currentVersion: '4.6.0',
|
|
},
|
|
});
|
|
expect(res1.x).toBeDefined();
|
|
});
|
|
it('checks if matchCurrentVersion selector works with regular expressions', () => {
|
|
const config: TestConfig = {
|
|
packageRules: [
|
|
{
|
|
matchPackageNames: ['test'],
|
|
matchCurrentVersion: '/^4/',
|
|
x: 1,
|
|
},
|
|
],
|
|
};
|
|
const res1 = applyPackageRules({
|
|
...config,
|
|
...{
|
|
depName: 'test',
|
|
currentValue: '4.6.0',
|
|
currentVersion: '4.6.0',
|
|
},
|
|
});
|
|
const res2 = applyPackageRules({
|
|
...config,
|
|
...{
|
|
depName: 'test',
|
|
currentValue: '5.6.0',
|
|
currentVersion: '5.6.0',
|
|
},
|
|
});
|
|
expect(res1.x).toBeDefined();
|
|
expect(res2.x).toBeUndefined();
|
|
});
|
|
it('checks if matchCurrentVersion selector works with negated regular expressions', () => {
|
|
const config: TestConfig = {
|
|
packageRules: [
|
|
{
|
|
matchPackageNames: ['test'],
|
|
matchCurrentVersion: '!/^4/',
|
|
x: 1,
|
|
},
|
|
],
|
|
};
|
|
const res1 = applyPackageRules({
|
|
...config,
|
|
...{
|
|
depName: 'test',
|
|
currentValue: '4.6.0',
|
|
currentVersion: '4.6.0',
|
|
},
|
|
});
|
|
const res2 = applyPackageRules({
|
|
...config,
|
|
...{
|
|
depName: 'test',
|
|
currentValue: '5.6.0',
|
|
currentVersion: '5.6.0',
|
|
},
|
|
});
|
|
expect(res1.x).toBeUndefined();
|
|
expect(res2.x).toBeDefined();
|
|
});
|
|
it('matches packageFiles', () => {
|
|
const config: TestConfig = {
|
|
packageFile: 'examples/foo/package.json',
|
|
packageRules: [
|
|
{
|
|
matchFiles: ['package.json'],
|
|
x: 1,
|
|
},
|
|
],
|
|
};
|
|
const res1 = applyPackageRules({
|
|
...config,
|
|
depName: 'test',
|
|
});
|
|
expect(res1.x).toBeUndefined();
|
|
config.packageFile = 'package.json';
|
|
const res2 = applyPackageRules({
|
|
...config,
|
|
depName: 'test',
|
|
});
|
|
expect(res2.x).toBeDefined();
|
|
});
|
|
it('matches lock files', () => {
|
|
const config: TestConfig = {
|
|
packageFile: 'examples/foo/package.json',
|
|
lockFiles: ['yarn.lock'],
|
|
packageRules: [
|
|
{
|
|
matchFiles: ['yarn.lock'],
|
|
x: 1,
|
|
},
|
|
],
|
|
};
|
|
const res = applyPackageRules(config);
|
|
expect(res.x).toBeDefined();
|
|
});
|
|
it('matches paths', () => {
|
|
const config: TestConfig = {
|
|
packageFile: 'examples/foo/package.json',
|
|
packageRules: [
|
|
{
|
|
matchPaths: ['examples/**', 'lib/'],
|
|
x: 1,
|
|
},
|
|
],
|
|
};
|
|
const res1 = applyPackageRules({
|
|
...config,
|
|
depName: 'test',
|
|
});
|
|
expect(res1.x).toBeDefined();
|
|
config.packageFile = 'package.json';
|
|
const res2 = applyPackageRules({
|
|
...config,
|
|
depName: 'test',
|
|
});
|
|
expect(res2.x).toBeUndefined();
|
|
config.packageFile = 'lib/a/package.json';
|
|
const res3 = applyPackageRules({
|
|
...config,
|
|
depName: 'test',
|
|
});
|
|
expect(res3.x).toBeDefined();
|
|
});
|
|
it('empty rules', () => {
|
|
// FIXME: explicit assert condition
|
|
expect(
|
|
applyPackageRules({ ...config1, packageRules: null })
|
|
).toMatchSnapshot();
|
|
});
|
|
|
|
it('creates groupSlug if necessary', () => {
|
|
const config: TestConfig = {
|
|
depName: 'foo',
|
|
packageRules: [
|
|
{
|
|
matchPackagePatterns: ['*'],
|
|
groupName: 'A',
|
|
groupSlug: 'a',
|
|
},
|
|
{
|
|
matchPackagePatterns: ['*'],
|
|
groupName: 'B',
|
|
},
|
|
],
|
|
};
|
|
const res = applyPackageRules(config);
|
|
expect(res.groupSlug).toBe('b');
|
|
});
|
|
it('matches matchSourceUrlPrefixes(case-insensitive)', () => {
|
|
const config: TestConfig = {
|
|
packageRules: [
|
|
{
|
|
matchSourceUrlPrefixes: [
|
|
'https://github.com/foo/bar',
|
|
'https://github.com/Renovatebot/',
|
|
],
|
|
x: 1,
|
|
},
|
|
],
|
|
};
|
|
const dep = {
|
|
depType: 'dependencies',
|
|
depName: 'a',
|
|
updateType: 'patch' as UpdateType,
|
|
sourceUrl: 'https://github.com/renovatebot/Presets',
|
|
};
|
|
const res = applyPackageRules({ ...config, ...dep });
|
|
expect(res.x).toBe(1);
|
|
});
|
|
});
|