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 { applyConstraintsFiltering, applyExtractVersion, applyVersionCompatibility, filterValidVersions, getDatasourceFor, 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('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(?.+)$'); 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(?.+)$'); 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 = '^(?[^-]+)$'; 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 = '^(?[^-]+)(?.*)?$'; 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 = '^(?[^-]+)(?.*)?$'; const res = applyExtractVersion(input, '^v(?.+)$'); expect( applyVersionCompatibility(res, versionCompatibility, '-alpine'), ).toMatchObject({ releases: [{ version: '3.0.0', versionOrig: 'v3.0.0-alpine' }], }); }); }); });