import upath from 'upath'; import { mockExecAll } from '~test/exec-util.ts'; import { fs, partial } from '~test/util.ts'; import { GlobalConfig } from '../../../../config/global.ts'; import type { InternalGlobalConfigOptions, RepoGlobalConfig, } from '../../../../config/types.ts'; import { TEMPORARY_ERROR } from '../../../../constants/error-messages.ts'; import { logger } from '../../../../logger/index.ts'; import * as hostRules from '../../../../util/host-rules.ts'; import { getPkgReleases as _getPkgReleases } from '../../../datasource/index.ts'; import type { UpdateArtifact, UpdateArtifactsConfig } from '../../types.ts'; import { parsePyProject } from '../extract.ts'; import { depTypes } from '../utils.ts'; import { PdmProcessor } from './pdm.ts'; vi.mock('../../../../util/fs/index.ts'); vi.mock('../../../datasource/index.ts'); const getPkgReleases = vi.mocked(_getPkgReleases); const config: UpdateArtifactsConfig = {}; const adminConfig: RepoGlobalConfig & InternalGlobalConfigOptions = { localDir: upath.join('/tmp/github/some/repo'), cacheDir: upath.join('/tmp/cache'), containerbaseDir: upath.join('/tmp/cache/containerbase'), binarySource: 'global', }; const processor = new PdmProcessor(); describe('modules/manager/pep621/processors/pdm', () => { describe('updateArtifacts()', () => { it('throws TEMPORARY_ERROR', async () => { fs.readLocalFile.mockRejectedValueOnce(new Error(TEMPORARY_ERROR)); const result = processor.updateArtifacts( partial({ config: {} }), partial(), ); await expect(result).rejects.toThrow(TEMPORARY_ERROR); }); it('return null if there is no lock file', async () => { fs.getSiblingFileName.mockReturnValueOnce('pdm.lock'); const updatedDeps = [{ packageName: 'dep1' }]; const result = await processor.updateArtifacts( { packageFileName: 'pyproject.toml', newPackageFileContent: '', config, updatedDeps, }, parsePyProject('')!, ); expect(result).toBeNull(); }); it('return null if the lock file is unchanged', async () => { const execSnapshots = mockExecAll(); GlobalConfig.set({ ...adminConfig, binarySource: 'docker', dockerSidecarImage: 'ghcr.io/renovatebot/base-image', }); fs.getSiblingFileName.mockReturnValueOnce('pdm.lock'); fs.readLocalFile.mockResolvedValueOnce('test content'); fs.readLocalFile.mockResolvedValueOnce('test content'); // python getPkgReleases.mockResolvedValueOnce({ releases: [{ version: '3.11.1' }, { version: '3.11.2' }], }); // pdm getPkgReleases.mockResolvedValueOnce({ releases: [{ version: 'v2.6.1' }, { version: 'v2.5.0' }], }); const updatedDeps = [{ packageName: 'dep1' }]; const result = await processor.updateArtifacts( { packageFileName: 'pyproject.toml', newPackageFileContent: '', config: { constraints: {} }, updatedDeps, }, parsePyProject('')!, ); expect(result).toBeNull(); expect(execSnapshots).toMatchObject([ { cmd: 'docker pull ghcr.io/renovatebot/base-image', }, { cmd: 'docker ps --filter name=renovate_sidecar -aq', }, { cmd: 'docker run --rm --name=renovate_sidecar --label=renovate_child ' + '-v "/tmp/github/some/repo":"/tmp/github/some/repo" ' + '-v "/tmp/cache":"/tmp/cache" ' + '-e CI ' + '-e CONTAINERBASE_CACHE_DIR ' + '-w "/tmp/github/some/repo" ' + 'ghcr.io/renovatebot/base-image ' + "bash -l -c '" + 'install-tool python 3.11.2 ' + '&& ' + 'install-tool pdm v2.5.0 ' + '&& ' + 'pdm update --no-sync --update-eager dep1' + "'", }, ]); }); it('returns artifact error', async () => { const execSnapshots = mockExecAll(); GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.getSiblingFileName.mockReturnValueOnce('pdm.lock'); fs.readLocalFile.mockImplementationOnce(() => { throw new Error('test error'); }); const updatedDeps = [{ packageName: 'dep1' }]; const result = await processor.updateArtifacts( { packageFileName: 'pyproject.toml', newPackageFileContent: '', config: {}, updatedDeps, }, parsePyProject('')!, ); expect(result).toEqual([ { artifactError: { fileName: 'pdm.lock', stderr: 'test error' } }, ]); expect(execSnapshots).toEqual([]); }); it('return update dep update', async () => { const execSnapshots = mockExecAll(); GlobalConfig.set(adminConfig); fs.getSiblingFileName.mockReturnValueOnce('pdm.lock'); fs.readLocalFile.mockResolvedValueOnce('test content'); fs.readLocalFile.mockResolvedValueOnce('changed test content'); // python getPkgReleases.mockResolvedValueOnce({ releases: [{ version: '3.11.1' }, { version: '3.11.2' }], }); // pdm getPkgReleases.mockResolvedValueOnce({ releases: [{ version: 'v2.6.1' }, { version: 'v2.5.0' }], }); const updatedDeps = [ { packageName: 'dep1', depType: depTypes.dependencies, }, { packageName: 'dep2', depType: depTypes.dependencies }, { packageName: 'dep3', managerData: { depGroup: 'group1' }, depType: depTypes.optionalDependencies, }, { packageName: 'dep4', depType: depTypes.optionalDependencies, managerData: { depGroup: 'group1' }, }, { packageName: 'dep5', depType: depTypes.pdmDevDependencies, managerData: { depGroup: 'group2' }, }, { packageName: 'dep6', depType: depTypes.pdmDevDependencies, managerData: { depGroup: 'group2' }, }, { packageName: 'dep7', depType: depTypes.pdmDevDependencies, managerData: { depGroup: 'group3' }, }, { packageName: 'dep8', depType: depTypes.pdmDevDependencies, managerData: { depGroup: 'group3' }, }, { packageName: 'dep9', depType: depTypes.buildSystemRequires }, { packageName: 'dep10', depType: depTypes.dependencyGroups, managerData: { depGroup: 'dev' }, }, ]; const result = await processor.updateArtifacts( { packageFileName: 'pyproject.toml', newPackageFileContent: '', config: {}, updatedDeps, }, parsePyProject('')!, ); expect(result).toEqual([ { file: { contents: 'changed test content', path: 'pdm.lock', type: 'addition', }, }, ]); expect(execSnapshots).toMatchObject([ { cmd: 'pdm update --no-sync --update-eager dep1 dep2', }, { cmd: 'pdm update --no-sync --update-eager -G group1 dep3 dep4', }, { cmd: 'pdm update --no-sync --update-eager -dG group2 dep5 dep6', }, { cmd: 'pdm update --no-sync --update-eager -dG group3 dep7 dep8', }, { cmd: 'pdm update --no-sync --update-eager -dG dev dep10', }, ]); }); it('discard dependencies if the devGroup is missing', async () => { const execSnapshots = mockExecAll(); GlobalConfig.set(adminConfig); fs.getSiblingFileName.mockReturnValueOnce('pdm.lock'); fs.readLocalFile.mockResolvedValueOnce('test content'); fs.readLocalFile.mockResolvedValueOnce('test content'); // python getPkgReleases.mockResolvedValueOnce({ releases: [{ version: '3.11.1' }, { version: '3.11.2' }], }); // pdm getPkgReleases.mockResolvedValueOnce({ releases: [{ version: 'v2.6.1' }, { version: 'v2.5.0' }], }); const updatedDeps = [ { packageName: 'dep3', depType: depTypes.optionalDependencies, }, { packageName: 'dep5', depType: depTypes.pdmDevDependencies, }, { packageName: 'dep10', depType: depTypes.dependencyGroups, }, ]; const result = await processor.updateArtifacts( { packageFileName: 'pyproject.toml', newPackageFileContent: '', config: {}, updatedDeps, }, parsePyProject('')!, ); expect(result).toBeNull(); expect(execSnapshots).toEqual([]); expect(logger.once.warn).toHaveBeenCalledTimes(3); }); it('return update on lockfileMaintenance', async () => { const execSnapshots = mockExecAll(); GlobalConfig.set(adminConfig); fs.getSiblingFileName.mockReturnValueOnce('pdm.lock'); fs.readLocalFile.mockResolvedValueOnce('test content'); fs.readLocalFile.mockResolvedValueOnce('changed test content'); // python getPkgReleases.mockResolvedValueOnce({ releases: [{ version: '3.11.1' }, { version: '3.11.2' }], }); // pdm getPkgReleases.mockResolvedValueOnce({ releases: [{ version: 'v2.6.1' }, { version: 'v2.5.0' }], }); const result = await processor.updateArtifacts( { packageFileName: 'folder/pyproject.toml', newPackageFileContent: '', config: { isLockFileMaintenance: true, }, updatedDeps: [], }, parsePyProject('')!, ); expect(result).toEqual([ { file: { contents: 'changed test content', path: 'pdm.lock', type: 'addition', }, }, ]); expect(execSnapshots).toMatchObject([ { cmd: 'pdm update --no-sync --update-eager', options: { cwd: '/tmp/github/some/repo/folder', }, }, ]); }); it('sets Git environment variables', async () => { hostRules.add({ matchHost: 'https://example.com', username: 'user', password: 'pass', }); const execSnapshots = mockExecAll(); GlobalConfig.set(adminConfig); fs.getSiblingFileName.mockReturnValueOnce('pdm.lock'); fs.readLocalFile.mockResolvedValueOnce('test content'); fs.readLocalFile.mockResolvedValueOnce('changed test content'); // python getPkgReleases.mockResolvedValueOnce({ releases: [{ version: '3.11.1' }, { version: '3.11.2' }], }); // pdm getPkgReleases.mockResolvedValueOnce({ releases: [{ version: 'v2.6.1' }, { version: 'v2.5.0' }], }); const result = await processor.updateArtifacts( { packageFileName: 'folder/pyproject.toml', newPackageFileContent: '', config: { isLockFileMaintenance: true, }, updatedDeps: [], }, parsePyProject('')!, ); expect(result).toEqual([ { file: { contents: 'changed test content', path: 'pdm.lock', type: 'addition', }, }, ]); expect(execSnapshots).toMatchObject([ { cmd: 'pdm update --no-sync --update-eager', options: { cwd: '/tmp/github/some/repo/folder', env: { GIT_CONFIG_COUNT: '3', GIT_CONFIG_KEY_0: 'url.https://user:pass@example.com/.insteadOf', GIT_CONFIG_KEY_1: 'url.https://user:pass@example.com/.insteadOf', GIT_CONFIG_KEY_2: 'url.https://user:pass@example.com/.insteadOf', GIT_CONFIG_VALUE_0: 'ssh://git@example.com/', GIT_CONFIG_VALUE_1: 'git@example.com:', GIT_CONFIG_VALUE_2: 'https://example.com/', }, }, }, ]); }); }); });