Files
Michael Kriese 320f770c36 fix(cache): hash URL credentials in cache keys
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>
2026-10-20 09:05:00 +01:00

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' }],
});
});
});
});