mirror of
https://github.com/renovatebot/renovate.git
synced 2026-10-25 15:01:49 +00:00
320f770c36
Co-Authored-By: Claude Fable 5.1 <michael.kriese+claude-code@mend.io> Co-Authored-By: Claude Opus 5 <michael.kriese+claude-code@mend.io> Co-Authored-By: Claude Opus 5.5 <michael.kriese+claude-code@mend.io>
449 lines
14 KiB
TypeScript
449 lines
14 KiB
TypeScript
import { logger } from '~test/util.ts';
|
|
import { defaultVersioning } from '../versioning/index.ts';
|
|
import ruby from '../versioning/ruby/index.ts';
|
|
import semverCoerced from '../versioning/semver-coerced/index.ts';
|
|
import datasources from './api.ts';
|
|
import {
|
|
applyConstraintsFiltering,
|
|
applyExtractVersion,
|
|
applyVersionCompatibility,
|
|
filterValidVersions,
|
|
getDatasourceFor,
|
|
getDatasourceHostType,
|
|
getDefaultVersioning,
|
|
isGetPkgReleasesConfig,
|
|
sortAndRemoveDuplicates,
|
|
} from './common.ts';
|
|
import { CustomDatasource } from './custom/index.ts';
|
|
import { NpmDatasource } from './npm/index.ts';
|
|
import type { GetPkgReleasesConfig, ReleaseResult } from './types.ts';
|
|
|
|
describe('modules/datasource/common', () => {
|
|
describe('getDatasourceFor', () => {
|
|
it('returns null for unknown datasource', () => {
|
|
expect(getDatasourceFor('foobar')).toBeNull();
|
|
});
|
|
|
|
it('supports custom datasource', () => {
|
|
expect(getDatasourceFor('custom.foobar')).toEqual(
|
|
getDatasourceFor(CustomDatasource.id),
|
|
);
|
|
});
|
|
|
|
it('returns datasource for known datasource', () => {
|
|
expect(getDatasourceFor('npm')).toMatchObject({
|
|
id: NpmDatasource.id,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('getDatasourceHostType', () => {
|
|
it.each`
|
|
datasource | expected
|
|
${'npm'} | ${'npm'}
|
|
${'custom.foobar'} | ${'custom'}
|
|
${'sbt-package'} | ${'sbt'}
|
|
${'sbt-plugin'} | ${'sbt'}
|
|
${'foobar'} | ${null}
|
|
`('returns $expected for $datasource', ({ datasource, expected }) => {
|
|
expect(getDatasourceHostType(datasource)).toBe(expected);
|
|
});
|
|
|
|
it.each([...datasources.keys()])(
|
|
'returns the host type of the http client of %s',
|
|
(id) => {
|
|
const { http } = datasources.get(id) as unknown as {
|
|
http?: { hostType: string };
|
|
};
|
|
|
|
expect(getDatasourceHostType(id)).toBe(http?.hostType ?? id);
|
|
},
|
|
);
|
|
});
|
|
|
|
describe('getDefaultVersioning', () => {
|
|
it('returns default versioning for undefined datasource', () => {
|
|
expect(getDefaultVersioning(undefined)).toBe(defaultVersioning.id);
|
|
});
|
|
|
|
it('returns default versioning for unknown datasource', () => {
|
|
expect(getDefaultVersioning('foobar')).toBe(defaultVersioning.id);
|
|
|
|
expect(logger.logger.warn).toHaveBeenCalledWith(
|
|
{ datasourceName: 'foobar' },
|
|
'Missing datasource!',
|
|
);
|
|
});
|
|
|
|
it('returns default versioning for datasource with missing default versioning configuration', () => {
|
|
expect(getDefaultVersioning('artifactory')).toBe(defaultVersioning.id);
|
|
});
|
|
|
|
it('returns datasource-defined default versioning', () => {
|
|
expect(getDefaultVersioning('crate')).toBe('cargo');
|
|
});
|
|
});
|
|
|
|
describe('isGetPkgReleasesConfig', () => {
|
|
it('returns true for valid input', () => {
|
|
const input = {
|
|
datasource: 'npm',
|
|
packageName: 'lodash',
|
|
};
|
|
expect(isGetPkgReleasesConfig(input)).toBe(true);
|
|
});
|
|
|
|
it('returns false for invalid input', () => {
|
|
const input = {
|
|
datasource: '',
|
|
packageName: 'lodash',
|
|
};
|
|
expect(isGetPkgReleasesConfig(input)).toBe(false);
|
|
});
|
|
|
|
it('returns false for input with missing properties', () => {
|
|
const input = {
|
|
datasource: 'npm',
|
|
};
|
|
expect(isGetPkgReleasesConfig(input)).toBe(false);
|
|
});
|
|
|
|
it('returns false for input with non-string properties', () => {
|
|
const input = {
|
|
datasource: 123,
|
|
packageName: 'lodash',
|
|
};
|
|
expect(isGetPkgReleasesConfig(input)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('applyExtractVersion', () => {
|
|
it('should return the same release result if extractVersion is not defined', () => {
|
|
const releaseResult: ReleaseResult = {
|
|
releases: [{ version: '1.0.0' }, { version: '2.0.0' }],
|
|
};
|
|
const res = applyExtractVersion(releaseResult, undefined);
|
|
expect(res).toBe(releaseResult);
|
|
});
|
|
|
|
it('should extract version from release using provided regex', () => {
|
|
const releaseResult: ReleaseResult = {
|
|
releases: [{ version: 'v1.0.0' }, { version: 'v2.0.0' }],
|
|
};
|
|
const res = applyExtractVersion(releaseResult, '^v(?<version>.+)$');
|
|
expect(res).toEqual({
|
|
releases: [
|
|
{ version: '1.0.0', versionOrig: 'v1.0.0' },
|
|
{ version: '2.0.0', versionOrig: 'v2.0.0' },
|
|
],
|
|
});
|
|
});
|
|
|
|
it('should return null for releases with invalid version', () => {
|
|
const releaseResult: ReleaseResult = {
|
|
releases: [{ version: 'v1.0.0' }, { version: 'invalid' }],
|
|
};
|
|
const result = applyExtractVersion(releaseResult, '^v(?<version>.+)$');
|
|
expect(result).toEqual({
|
|
releases: [{ version: '1.0.0', versionOrig: 'v1.0.0' }],
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('filterValidVersions', () => {
|
|
const releaseResult: ReleaseResult = {
|
|
releases: [
|
|
{ version: '1.0.0' },
|
|
{ version: '2.0.0' },
|
|
{ version: 'invalid' },
|
|
],
|
|
};
|
|
|
|
it('should filter out invalid versions', () => {
|
|
const config = { datasource: 'npm' };
|
|
const res = filterValidVersions(releaseResult, config);
|
|
expect(res).toEqual({
|
|
releases: [{ version: '1.0.0' }, { version: '2.0.0' }],
|
|
});
|
|
});
|
|
|
|
it('should use default versioning if none is specified', () => {
|
|
const config = { datasource: 'foobar' };
|
|
const res = filterValidVersions(releaseResult, config);
|
|
expect(res).toEqual({
|
|
releases: [{ version: '1.0.0' }, { version: '2.0.0' }],
|
|
});
|
|
});
|
|
|
|
it('should use specified versioning if provided', () => {
|
|
const config = { datasource: 'npm', versioning: 'semver' };
|
|
const res = filterValidVersions(releaseResult, config);
|
|
expect(res).toEqual({
|
|
releases: [{ version: '1.0.0' }, { version: '2.0.0' }],
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('sortAndRemoveDuplicates', () => {
|
|
it('sorts releases by version and removes duplicates', () => {
|
|
const config = { datasource: 'npm' };
|
|
const releaseResult: ReleaseResult = {
|
|
releases: [
|
|
{ version: '2.0.0' },
|
|
{ version: '1.0.0' },
|
|
{ version: '1.0.0' },
|
|
{ version: '3.0.0' },
|
|
],
|
|
};
|
|
const expected: ReleaseResult = {
|
|
releases: [
|
|
{ version: '1.0.0' },
|
|
{ version: '2.0.0' },
|
|
{ version: '3.0.0' },
|
|
],
|
|
};
|
|
const result = sortAndRemoveDuplicates(releaseResult, config);
|
|
expect(result).toEqual(expected);
|
|
});
|
|
|
|
it('uses default versioning if none is specified', () => {
|
|
const config = { datasource: 'foobar' };
|
|
const releaseResult: ReleaseResult = {
|
|
releases: [{ version: '1.0.0' }, { version: '2.0.0' }],
|
|
};
|
|
const result = sortAndRemoveDuplicates(releaseResult, config);
|
|
expect(result).toEqual({
|
|
releases: [{ version: '1.0.0' }, { version: '2.0.0' }],
|
|
});
|
|
|
|
expect(logger.logger.warn).toHaveBeenCalledWith(
|
|
{ datasourceName: 'foobar' },
|
|
'Missing datasource!',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('applyConstraintsFiltering', () => {
|
|
it('should remove constraints from releases if constraintsFiltering is not strict', () => {
|
|
const config = {
|
|
datasource: 'foo',
|
|
packageName: 'bar',
|
|
constraintsFiltering: 'none' as const,
|
|
};
|
|
const releaseResult: ReleaseResult = {
|
|
releases: [
|
|
{
|
|
version: '1.0.0',
|
|
constraints: {
|
|
// @ts-expect-error -- intentionally using invalid constraint names
|
|
foo: ['^1.0.0'],
|
|
},
|
|
},
|
|
{
|
|
version: '2.0.0',
|
|
constraints: {
|
|
// @ts-expect-error -- intentionally using invalid constraint names
|
|
foo: ['^2.0.0'],
|
|
},
|
|
},
|
|
],
|
|
};
|
|
expect(applyConstraintsFiltering(releaseResult, config)).toEqual({
|
|
releases: [{ version: '1.0.0' }, { version: '2.0.0' }],
|
|
});
|
|
});
|
|
|
|
it('should filter releases based on constraints if constraintsFiltering is strict', () => {
|
|
const config = {
|
|
datasource: 'foo',
|
|
packageName: 'bar',
|
|
constraintsFiltering: 'strict' as const,
|
|
// oxlint-disable-next-line renovate/prefer-partial-in-specs -- intentionally using invalid constraint names
|
|
constraints: { baz: '^1.0.0', qux: 'invalid' } as never,
|
|
};
|
|
const releaseResult = {
|
|
releases: [
|
|
{ version: '1.0.0' },
|
|
{
|
|
version: '2.0.0',
|
|
// oxlint-disable-next-line renovate/prefer-partial-in-specs -- intentionally using invalid constraint value
|
|
constraints: { baz: [undefined] } as never,
|
|
},
|
|
{ version: '3.0.0', constraints: { baz: ['^0.9.0', 'invalid'] } },
|
|
],
|
|
};
|
|
// @ts-expect-error -- intentionally using invalid constraint names
|
|
expect(applyConstraintsFiltering(releaseResult, config)).toEqual({
|
|
releases: [{ version: '1.0.0' }, { version: '2.0.0' }],
|
|
});
|
|
});
|
|
|
|
it('should return all releases when no configConstraints', () => {
|
|
const config = {
|
|
datasource: 'pypi',
|
|
packageName: 'bar',
|
|
versioning: 'pep440',
|
|
constraintsFiltering: 'strict' as const,
|
|
};
|
|
const releaseResult = {
|
|
releases: [
|
|
{ version: '1.0.0', constraints: { python: ['^1.0.0'] } },
|
|
{ version: '2.0.0' },
|
|
],
|
|
};
|
|
expect(applyConstraintsFiltering(releaseResult, config)).toEqual({
|
|
releases: [{ version: '1.0.0' }, { version: '2.0.0' }],
|
|
});
|
|
});
|
|
|
|
it('should match exact constraints', () => {
|
|
const config = {
|
|
datasource: 'pypi',
|
|
packageName: 'bar',
|
|
versioning: 'pep440',
|
|
constraintsFiltering: 'strict' as const,
|
|
constraints: { python: '>=3.8' },
|
|
};
|
|
const releaseResult = {
|
|
releases: [
|
|
{ version: '1.0.0', constraints: { python: ['^1.0.0'] } },
|
|
{ version: '2.0.0', constraints: { python: ['>=3.8'] } },
|
|
],
|
|
};
|
|
expect(applyConstraintsFiltering(releaseResult, config)).toEqual({
|
|
releases: [{ version: '2.0.0' }],
|
|
});
|
|
});
|
|
|
|
it('should handle config with a range constraint, and a release with an exact version', () => {
|
|
const config = {
|
|
datasource: 'pypi',
|
|
packageName: 'bar',
|
|
versioning: 'pep440',
|
|
constraintsFiltering: 'strict' as const,
|
|
constraints: { python: '>=3.8' },
|
|
};
|
|
const releaseResult = {
|
|
releases: [
|
|
{ version: '1.0.0', constraints: { python: ['1.0.0'] } },
|
|
{ version: '2.0.0', constraints: { python: ['3.8.1'] } },
|
|
],
|
|
};
|
|
expect(applyConstraintsFiltering(releaseResult, config)).toEqual({
|
|
releases: [{ version: '2.0.0' }],
|
|
});
|
|
});
|
|
|
|
it('should handle config with an exact version, and a release with a range constraint', () => {
|
|
const config = {
|
|
datasource: 'pypi',
|
|
packageName: 'bar',
|
|
versioning: 'pep440',
|
|
constraintsFiltering: 'strict' as const,
|
|
constraints: { python: '3.8.1' },
|
|
};
|
|
const releaseResult = {
|
|
releases: [
|
|
{ version: '1.0.0', constraints: { python: ['1.0.0'] } },
|
|
{ version: '2.0.0', constraints: { python: ['3.8.1'] } },
|
|
],
|
|
};
|
|
expect(applyConstraintsFiltering(releaseResult, config)).toEqual({
|
|
releases: [{ version: '2.0.0' }],
|
|
});
|
|
});
|
|
|
|
it(`should allow constraintsVersioning to override the datasource's default versioning`, () => {
|
|
const config: GetPkgReleasesConfig = {
|
|
datasource: 'rubygems',
|
|
packageName: 'rails',
|
|
versioning: 'ruby',
|
|
constraintsFiltering: 'strict' as const,
|
|
constraints: {
|
|
// anything between 1.3.0 and less than 2.0.0
|
|
rubygems: '^1.3',
|
|
},
|
|
constraintsVersioning: {
|
|
// instead of using ruby version, which doesn't support this syntax
|
|
rubygems: 'semver-coerced',
|
|
},
|
|
};
|
|
|
|
// make sure that it's currently an invalid constraint for ruby
|
|
expect(config.constraints?.rubygems).toBeDefined();
|
|
expect(ruby.isValid(config.constraints!.rubygems!)).toBeFalse();
|
|
// but that it works when using semver-coerced
|
|
expect(semverCoerced.isValid(config.constraints!.rubygems!)).toBeTrue();
|
|
|
|
const releaseResult = {
|
|
releases: [
|
|
// slightly modified from upstream
|
|
{ version: '0.9.1', constraints: { rubygems: ['1.0.0'] } },
|
|
{ version: '3.1.3', constraints: { rubygems: ['1.2.3'] } },
|
|
|
|
// using upstraem data
|
|
{ version: '4.1.2', constraints: { rubygems: ['>= 1.8.11'] } },
|
|
{ version: '8.1.3', constraints: { rubygems: ['>= 1.8.11'] } },
|
|
],
|
|
};
|
|
expect(applyConstraintsFiltering(releaseResult, config)).toEqual({
|
|
releases: [{ version: '4.1.2' }, { version: '8.1.3' }],
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('applyVersionCompatibility', () => {
|
|
let input: ReleaseResult;
|
|
|
|
beforeEach(() => {
|
|
input = {
|
|
releases: [
|
|
{ version: '1.0.0' },
|
|
{ version: '2.0.0' },
|
|
{ version: '2.0.0-alpine' },
|
|
{ version: 'v3.0.0-alpine' },
|
|
],
|
|
};
|
|
});
|
|
|
|
it('returns immediately if no versionCompatibility', () => {
|
|
const result = applyVersionCompatibility(input, undefined, undefined);
|
|
expect(result).toBe(input);
|
|
});
|
|
|
|
it('filters out non-matching', () => {
|
|
const versionCompatibility = '^(?<version>[^-]+)$';
|
|
expect(
|
|
applyVersionCompatibility(input, versionCompatibility, undefined),
|
|
).toMatchObject({
|
|
releases: [
|
|
{ version: '1.0.0', versionOrig: '1.0.0' },
|
|
{ version: '2.0.0', versionOrig: '2.0.0' },
|
|
],
|
|
});
|
|
});
|
|
|
|
it('filters out incompatible', () => {
|
|
const versionCompatibility = '^(?<version>[^-]+)(?<compatibility>.*)?$';
|
|
expect(
|
|
applyVersionCompatibility(input, versionCompatibility, '-alpine'),
|
|
).toMatchObject({
|
|
releases: [
|
|
{ version: '2.0.0', versionOrig: '2.0.0-alpine' },
|
|
{ version: 'v3.0.0', versionOrig: 'v3.0.0-alpine' },
|
|
],
|
|
});
|
|
});
|
|
|
|
it('does not override versionOrig from extractVersion', () => {
|
|
const versionCompatibility = '^(?<version>[^-]+)(?<compatibility>.*)?$';
|
|
const res = applyExtractVersion(input, '^v(?<version>.+)$');
|
|
expect(
|
|
applyVersionCompatibility(res, versionCompatibility, '-alpine'),
|
|
).toMatchObject({
|
|
releases: [{ version: '3.0.0', versionOrig: 'v3.0.0-alpine' }],
|
|
});
|
|
});
|
|
});
|
|
});
|