mirror of
https://github.com/renovatebot/renovate.git
synced 2026-11-01 02:00:10 +00:00
9d1070091a
* test(manager/pep621): cover array uv sources * fix(manager/pep621): support array uv sources * test(manager/pep621): cover unknown source fallback
1044 lines
30 KiB
TypeScript
1044 lines
30 KiB
TypeScript
import { codeBlock } from 'common-tags';
|
|
import { GoogleAuth as _googleAuth } from 'google-auth-library';
|
|
import upath from 'upath';
|
|
import { mockExecAll } from '~test/exec-util.ts';
|
|
import { fs, hostRules, logger, 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 { GitRefsDatasource } from '../../../datasource/git-refs/index.ts';
|
|
import { GitTagsDatasource } from '../../../datasource/git-tags/index.ts';
|
|
import { GithubTagsDatasource } from '../../../datasource/github-tags/index.ts';
|
|
import { GitlabTagsDatasource } from '../../../datasource/gitlab-tags/index.ts';
|
|
import { getPkgReleases as _getPkgReleases } from '../../../datasource/index.ts';
|
|
import { PypiDatasource } from '../../../datasource/pypi/index.ts';
|
|
import type { UpdateArtifact, UpdateArtifactsConfig } from '../../types.ts';
|
|
import { parsePyProject } from '../extract.ts';
|
|
import type { PyProject, UvSource } from '../schema.ts';
|
|
import { depTypes } from '../utils.ts';
|
|
import { UvProcessor } from './uv.ts';
|
|
|
|
vi.mock('google-auth-library');
|
|
vi.mock('../../../../util/fs/index.ts');
|
|
vi.mock('../../../datasource/index.ts');
|
|
|
|
const googleAuth = vi.mocked(_googleAuth);
|
|
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 UvProcessor();
|
|
|
|
describe('modules/manager/pep621/processors/uv', () => {
|
|
describe('process()', () => {
|
|
it('returns initial dependencies if there is no tool.uv section', () => {
|
|
const pyproject = parsePyProject(codeBlock`
|
|
[tool]
|
|
`)!;
|
|
|
|
const dependencies = [{ depName: 'dep1' }];
|
|
|
|
const result = processor.process(pyproject, dependencies);
|
|
|
|
expect(result).toEqual(dependencies);
|
|
});
|
|
|
|
it('includes uv dev dependencies if there is a tool.uv section', () => {
|
|
const pyproject = parsePyProject(codeBlock`
|
|
[tool.uv]
|
|
dev-dependencies = ["dep2==1.2.3", "dep3==2.3.4"]
|
|
`)!;
|
|
|
|
const dependencies = [{ depName: 'dep1' }];
|
|
|
|
const result = processor.process(pyproject, dependencies);
|
|
|
|
expect(result).toEqual([
|
|
{ depName: 'dep1' },
|
|
{
|
|
currentValue: '==1.2.3',
|
|
currentVersion: '1.2.3',
|
|
datasource: 'pypi',
|
|
depName: 'dep2',
|
|
depType: 'tool.uv.dev-dependencies',
|
|
packageName: 'dep2',
|
|
},
|
|
{
|
|
currentValue: '==2.3.4',
|
|
currentVersion: '2.3.4',
|
|
datasource: 'pypi',
|
|
depName: 'dep3',
|
|
depType: 'tool.uv.dev-dependencies',
|
|
packageName: 'dep3',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('applies git sources', () => {
|
|
const pyproject = parsePyProject(codeBlock`
|
|
[tool.uv]
|
|
dev-dependencies = ["dep3", "dep4", "dep5"]
|
|
|
|
[tool.uv.sources]
|
|
dep1 = { git = "https://github.com/foo/dep1", tag = "0.1.0" }
|
|
dep2 = { git = "https://gitlab.com/foo/dep2", tag = "0.2.0" }
|
|
dep3 = { git = "https://codeberg.org/foo/dep3.git", tag = "0.3.0" }
|
|
dep4 = { git = "https://github.com/foo/dep4", rev = "1ca7d263f0f5038b53f74c5a757f18b8106c9390" }
|
|
dep5 = { git = "https://github.com/foo/dep5", branch = "master" }
|
|
`)!;
|
|
|
|
const dependencies = [
|
|
{
|
|
depName: 'dep1',
|
|
packageName: 'dep1',
|
|
},
|
|
{
|
|
depName: 'dep2',
|
|
packageName: 'dep2',
|
|
},
|
|
];
|
|
|
|
const result = processor.process(pyproject, dependencies);
|
|
|
|
expect(result).toEqual([
|
|
{
|
|
depName: 'dep1',
|
|
depType: depTypes.uvSources,
|
|
datasource: GithubTagsDatasource.id,
|
|
registryUrls: ['https://github.com'],
|
|
packageName: 'foo/dep1',
|
|
currentValue: '0.1.0',
|
|
},
|
|
{
|
|
depName: 'dep2',
|
|
depType: depTypes.uvSources,
|
|
datasource: GitlabTagsDatasource.id,
|
|
registryUrls: ['https://gitlab.com'],
|
|
packageName: 'foo/dep2',
|
|
currentValue: '0.2.0',
|
|
},
|
|
{
|
|
depName: 'dep3',
|
|
depType: depTypes.uvSources,
|
|
datasource: GitTagsDatasource.id,
|
|
packageName: 'https://codeberg.org/foo/dep3.git',
|
|
currentValue: '0.3.0',
|
|
},
|
|
{
|
|
depName: 'dep4',
|
|
depType: depTypes.uvSources,
|
|
datasource: GitRefsDatasource.id,
|
|
packageName: 'https://github.com/foo/dep4',
|
|
currentDigest: '1ca7d263f0f5038b53f74c5a757f18b8106c9390',
|
|
replaceString: '1ca7d263f0f5038b53f74c5a757f18b8106c9390',
|
|
},
|
|
{
|
|
depName: 'dep5',
|
|
depType: depTypes.uvSources,
|
|
datasource: GitRefsDatasource.id,
|
|
packageName: 'https://github.com/foo/dep5',
|
|
currentValue: 'master',
|
|
skipReason: 'git-dependency',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('pinned to non-default index', () => {
|
|
const pyproject = parsePyProject(codeBlock`
|
|
[tool.uv.sources]
|
|
dep1 = { index = "foo" }
|
|
dep2 = { index = "bar" }
|
|
|
|
[[tool.uv.index]]
|
|
name = "foo"
|
|
url = "https://foo.com/simple"
|
|
default = false
|
|
explicit = true
|
|
|
|
[[tool.uv.index]]
|
|
name = "bar"
|
|
url = "https://bar.com/simple"
|
|
default = false
|
|
explicit = true
|
|
|
|
[[tool.uv.index]]
|
|
name = "baz"
|
|
url = "https://baz.com/simple"
|
|
default = false
|
|
explicit = false
|
|
`)!;
|
|
|
|
const dependencies = [
|
|
{
|
|
depName: 'dep1',
|
|
packageName: 'dep1',
|
|
},
|
|
{
|
|
depName: 'dep2',
|
|
packageName: 'dep2',
|
|
},
|
|
{
|
|
depName: 'dep3',
|
|
packageName: 'dep3',
|
|
},
|
|
{
|
|
depName: 'dep4',
|
|
packageName: 'dep4',
|
|
},
|
|
];
|
|
|
|
const result = processor.process(pyproject, dependencies);
|
|
|
|
expect(result).toEqual([
|
|
{
|
|
depName: 'dep1',
|
|
depType: depTypes.uvSources,
|
|
registryUrls: ['https://foo.com/simple'],
|
|
packageName: 'dep1',
|
|
},
|
|
{
|
|
depName: 'dep2',
|
|
depType: depTypes.uvSources,
|
|
registryUrls: ['https://bar.com/simple'],
|
|
packageName: 'dep2',
|
|
},
|
|
{
|
|
depName: 'dep3',
|
|
packageName: 'dep3',
|
|
registryUrls: ['https://baz.com/simple', 'https://pypi.org/pypi/'],
|
|
},
|
|
{
|
|
depName: 'dep4',
|
|
registryUrls: ['https://baz.com/simple', 'https://pypi.org/pypi/'],
|
|
packageName: 'dep4',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('handles the array form of uv sources', () => {
|
|
const pyproject = parsePyProject(codeBlock`
|
|
[tool.uv.sources]
|
|
dep1 = [{ index = "foo" }]
|
|
dep2 = [
|
|
{ index = "foo", marker = "sys_platform == 'darwin'" },
|
|
{ index = "bar", marker = "sys_platform == 'linux'" },
|
|
]
|
|
dep3 = [{ git = "https://github.com/foo/dep3", tag = "0.3.0" }]
|
|
dep4 = [
|
|
{ index = "foo", marker = "sys_platform == 'darwin'" },
|
|
{ git = "https://github.com/foo/dep4", marker = "sys_platform == 'linux'" },
|
|
]
|
|
dep5 = [
|
|
{ index = "unknown", marker = "sys_platform == 'darwin'" },
|
|
{ index = "foo", marker = "sys_platform == 'linux'" },
|
|
]
|
|
dep6 = [
|
|
{ index = "unknown", marker = "sys_platform == 'darwin'" },
|
|
{ index = "unknown2", marker = "sys_platform == 'linux'" },
|
|
]
|
|
dep7 = [{ index = "unknown" }]
|
|
|
|
[[tool.uv.index]]
|
|
name = "foo"
|
|
url = "https://foo.com/simple"
|
|
default = false
|
|
explicit = true
|
|
|
|
[[tool.uv.index]]
|
|
name = "bar"
|
|
url = "https://bar.com/simple"
|
|
default = false
|
|
explicit = true
|
|
`)!;
|
|
|
|
const dependencies = [
|
|
{
|
|
depName: 'dep1',
|
|
packageName: 'dep1',
|
|
},
|
|
{
|
|
depName: 'dep2',
|
|
packageName: 'dep2',
|
|
},
|
|
{
|
|
depName: 'dep3',
|
|
packageName: 'dep3',
|
|
},
|
|
{
|
|
depName: 'dep4',
|
|
packageName: 'dep4',
|
|
},
|
|
{
|
|
depName: 'dep5',
|
|
packageName: 'dep5',
|
|
},
|
|
{
|
|
depName: 'dep6',
|
|
packageName: 'dep6',
|
|
},
|
|
{
|
|
depName: 'dep7',
|
|
packageName: 'dep7',
|
|
},
|
|
];
|
|
|
|
const result = processor.process(pyproject, dependencies);
|
|
|
|
expect(result).toEqual([
|
|
{
|
|
depName: 'dep1',
|
|
depType: depTypes.uvSources,
|
|
registryUrls: ['https://foo.com/simple'],
|
|
packageName: 'dep1',
|
|
},
|
|
{
|
|
depName: 'dep2',
|
|
depType: depTypes.uvSources,
|
|
registryUrls: ['https://foo.com/simple', 'https://bar.com/simple'],
|
|
packageName: 'dep2',
|
|
},
|
|
{
|
|
depName: 'dep3',
|
|
depType: depTypes.uvSources,
|
|
datasource: GithubTagsDatasource.id,
|
|
registryUrls: ['https://github.com'],
|
|
packageName: 'foo/dep3',
|
|
currentValue: '0.3.0',
|
|
},
|
|
{
|
|
depName: 'dep4',
|
|
depType: depTypes.uvSources,
|
|
skipReason: 'unsupported',
|
|
packageName: 'dep4',
|
|
},
|
|
{
|
|
depName: 'dep5',
|
|
depType: depTypes.uvSources,
|
|
registryUrls: ['https://foo.com/simple'],
|
|
packageName: 'dep5',
|
|
},
|
|
{
|
|
depName: 'dep6',
|
|
depType: depTypes.uvSources,
|
|
packageName: 'dep6',
|
|
},
|
|
{
|
|
depName: 'dep7',
|
|
depType: depTypes.uvSources,
|
|
packageName: 'dep7',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('skips source types the schema does not produce', () => {
|
|
// Cannot be expressed in pyproject.toml, as the schema only emits the
|
|
// known source shapes. Exercises the defensive fallback.
|
|
const uv = partial<NonNullable<NonNullable<PyProject['tool']>['uv']>>({
|
|
sources: { dep1: [partial<UvSource>({})] },
|
|
});
|
|
const pyproject = partial<PyProject>({ tool: { uv } });
|
|
|
|
const dependencies = [
|
|
{
|
|
depName: 'dep1',
|
|
packageName: 'dep1',
|
|
},
|
|
];
|
|
|
|
const result = processor.process(pyproject, dependencies);
|
|
|
|
expect(result).toEqual([
|
|
{
|
|
depName: 'dep1',
|
|
depType: depTypes.uvSources,
|
|
packageName: 'dep1',
|
|
skipReason: 'unknown-registry',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('index with optional name', () => {
|
|
const pyproject = parsePyProject(codeBlock`
|
|
[[tool.uv.index]]
|
|
url = "https://foo.com/simple"
|
|
default = true
|
|
explicit = false
|
|
`)!;
|
|
|
|
const dependencies = [
|
|
{
|
|
depName: 'dep1',
|
|
packageName: 'dep1',
|
|
},
|
|
{
|
|
depName: 'dep2',
|
|
packageName: 'dep2',
|
|
},
|
|
];
|
|
|
|
const result = processor.process(pyproject, dependencies);
|
|
|
|
expect(result).toEqual([
|
|
{
|
|
depName: 'dep1',
|
|
registryUrls: ['https://foo.com/simple'],
|
|
packageName: 'dep1',
|
|
},
|
|
{
|
|
depName: 'dep2',
|
|
registryUrls: ['https://foo.com/simple'],
|
|
packageName: 'dep2',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('override implicit default index', () => {
|
|
const pyproject = parsePyProject(codeBlock`
|
|
[[tool.uv.index]]
|
|
name = "foo"
|
|
url = "https://foo.com/simple"
|
|
default = true
|
|
explicit = false
|
|
`)!;
|
|
|
|
const dependencies = [
|
|
{
|
|
depName: 'python',
|
|
packageName: 'python',
|
|
depType: 'requires-python',
|
|
},
|
|
{
|
|
depName: 'dep1',
|
|
packageName: 'dep1',
|
|
},
|
|
{
|
|
depName: 'dep2',
|
|
packageName: 'dep2',
|
|
},
|
|
];
|
|
|
|
const result = processor.process(pyproject, dependencies);
|
|
|
|
expect(result).toEqual([
|
|
{
|
|
depName: 'python',
|
|
depType: 'requires-python',
|
|
packageName: 'python',
|
|
},
|
|
{
|
|
depName: 'dep1',
|
|
registryUrls: ['https://foo.com/simple'],
|
|
packageName: 'dep1',
|
|
},
|
|
{
|
|
depName: 'dep2',
|
|
registryUrls: ['https://foo.com/simple'],
|
|
packageName: 'dep2',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('override explicit default index', () => {
|
|
const pyproject = parsePyProject(codeBlock`
|
|
[tool.uv.sources]
|
|
dep1 = { index = "foo" }
|
|
|
|
[[tool.uv.index]]
|
|
name = "foo"
|
|
url = "https://foo.com/simple"
|
|
default = true
|
|
explicit = true
|
|
`)!;
|
|
|
|
const dependencies = [
|
|
{
|
|
depName: 'dep1',
|
|
packageName: 'dep1',
|
|
},
|
|
{
|
|
depName: 'dep2',
|
|
packageName: 'dep2',
|
|
},
|
|
];
|
|
|
|
const result = processor.process(pyproject, dependencies);
|
|
|
|
expect(result).toEqual([
|
|
{
|
|
depName: 'dep1',
|
|
depType: depTypes.uvSources,
|
|
registryUrls: ['https://foo.com/simple'],
|
|
packageName: 'dep1',
|
|
},
|
|
{
|
|
depName: 'dep2',
|
|
registryUrls: [],
|
|
packageName: 'dep2',
|
|
},
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('extractLockedVersions()', () => {
|
|
it('returns if no lockfile found', async () => {
|
|
fs.findLocalSiblingOrParent.mockResolvedValueOnce(null);
|
|
const result = await processor.extractLockedVersions(
|
|
partial(),
|
|
[],
|
|
'pyproject.toml',
|
|
);
|
|
expect(result).toBeEmptyArray();
|
|
|
|
expect(logger.logger.debug).toHaveBeenCalledWith(
|
|
{ packageFile: 'pyproject.toml' },
|
|
'No uv lock file found',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('updateArtifacts()', () => {
|
|
it('throws TEMPORARY_ERROR', async () => {
|
|
fs.readLocalFile.mockRejectedValueOnce(new Error(TEMPORARY_ERROR));
|
|
const result = processor.updateArtifacts(
|
|
partial<UpdateArtifact>({ config: {} }),
|
|
partial(),
|
|
);
|
|
await expect(result).rejects.toThrow(TEMPORARY_ERROR);
|
|
});
|
|
|
|
it('returns if no lockfile found', async () => {
|
|
fs.findLocalSiblingOrParent.mockResolvedValueOnce(null);
|
|
const result = await processor.updateArtifacts(
|
|
partial<UpdateArtifact>({ config: {} }),
|
|
partial(),
|
|
);
|
|
expect(result).toBeNull();
|
|
|
|
expect(logger.logger.debug).toHaveBeenCalledWith(
|
|
{ packageFileName: undefined },
|
|
'No uv lock file found',
|
|
);
|
|
});
|
|
|
|
it('returns null if there is no lock file', async () => {
|
|
fs.findLocalSiblingOrParent.mockResolvedValueOnce('uv.lock');
|
|
const updatedDeps = [{ packageName: 'dep1' }];
|
|
const result = await processor.updateArtifacts(
|
|
{
|
|
packageFileName: 'pyproject.toml',
|
|
newPackageFileContent: '',
|
|
config,
|
|
updatedDeps,
|
|
},
|
|
parsePyProject('')!,
|
|
);
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('returns null if the lock file is unchanged', async () => {
|
|
const execSnapshots = mockExecAll();
|
|
GlobalConfig.set({
|
|
...adminConfig,
|
|
binarySource: 'docker',
|
|
dockerSidecarImage: 'ghcr.io/renovatebot/base-image',
|
|
});
|
|
fs.findLocalSiblingOrParent.mockResolvedValueOnce('uv.lock');
|
|
fs.readLocalFile.mockResolvedValueOnce('test content');
|
|
fs.readLocalFile.mockResolvedValueOnce('test content');
|
|
// python
|
|
getPkgReleases.mockResolvedValueOnce({
|
|
releases: [{ version: '3.11.1' }, { version: '3.11.2' }],
|
|
});
|
|
// uv
|
|
getPkgReleases.mockResolvedValueOnce({
|
|
releases: [{ version: '0.2.35' }, { version: '0.2.28' }],
|
|
});
|
|
|
|
const updatedDeps = [{ packageName: 'dep1' }];
|
|
const pyproject = parsePyProject(codeBlock`
|
|
[project]
|
|
requires-python = "==3.11.1"
|
|
|
|
[tool.uv]
|
|
required-version = "==0.2.35"
|
|
`)!;
|
|
|
|
const result = await processor.updateArtifacts(
|
|
{
|
|
packageFileName: 'pyproject.toml',
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
updatedDeps,
|
|
},
|
|
pyproject,
|
|
);
|
|
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.1 ' +
|
|
'&& ' +
|
|
'install-tool uv 0.2.35 ' +
|
|
'&& ' +
|
|
'uv lock --upgrade-package dep1' +
|
|
"'",
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('returns artifact error', async () => {
|
|
const execSnapshots = mockExecAll();
|
|
GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
|
|
fs.findLocalSiblingOrParent.mockResolvedValueOnce('uv.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: 'uv.lock', stderr: 'test error' } },
|
|
]);
|
|
expect(execSnapshots).toEqual([]);
|
|
});
|
|
|
|
it('return update dep update', async () => {
|
|
const execSnapshots = mockExecAll();
|
|
GlobalConfig.set(adminConfig);
|
|
fs.findLocalSiblingOrParent.mockResolvedValueOnce('uv.lock');
|
|
fs.readLocalFile.mockResolvedValueOnce('test content');
|
|
fs.readLocalFile.mockResolvedValueOnce('changed test content');
|
|
// python
|
|
getPkgReleases.mockResolvedValueOnce({
|
|
releases: [{ version: '3.11.1' }, { version: '3.11.2' }],
|
|
});
|
|
// uv
|
|
getPkgReleases.mockResolvedValueOnce({
|
|
releases: [{ version: '0.2.35' }, { version: '0.2.28' }],
|
|
});
|
|
|
|
const updatedDeps = [
|
|
{ packageName: 'dep1', depType: depTypes.dependencies },
|
|
{ packageName: 'dep2', depType: depTypes.dependencies },
|
|
{ depName: 'dep3', depType: depTypes.optionalDependencies },
|
|
{ depName: 'dep4', depType: depTypes.optionalDependencies },
|
|
{ depName: 'dep5', depType: depTypes.uvDevDependencies },
|
|
{ depName: 'dep6', depType: depTypes.uvDevDependencies },
|
|
{ depName: 'dep7', depType: depTypes.buildSystemRequires },
|
|
];
|
|
const result = await processor.updateArtifacts(
|
|
{
|
|
packageFileName: 'pyproject.toml',
|
|
newPackageFileContent: '',
|
|
config: {
|
|
constraints: {},
|
|
},
|
|
updatedDeps,
|
|
},
|
|
parsePyProject('')!,
|
|
);
|
|
expect(result).toEqual([
|
|
{
|
|
file: {
|
|
contents: 'changed test content',
|
|
path: 'uv.lock',
|
|
type: 'addition',
|
|
},
|
|
},
|
|
]);
|
|
expect(execSnapshots).toMatchObject([
|
|
{
|
|
cmd: 'uv lock --upgrade-package dep1 --upgrade-package dep2 --upgrade-package dep3 --upgrade-package dep4 --upgrade-package dep5 --upgrade-package dep6',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('performs update on private package registry', async () => {
|
|
const execSnapshots = mockExecAll();
|
|
GlobalConfig.set(adminConfig);
|
|
hostRules.add({
|
|
matchHost: 'https://example.com',
|
|
username: 'user',
|
|
password: 'pass',
|
|
});
|
|
hostRules.add({
|
|
matchHost: 'https://pinned.com/simple',
|
|
username: 'user',
|
|
password: 'pass',
|
|
});
|
|
googleAuth.mockImplementationOnce(
|
|
// TODO: fix typing
|
|
vi.fn<any>(
|
|
class {
|
|
getAccessToken = vi.fn().mockResolvedValue('some-token');
|
|
},
|
|
),
|
|
);
|
|
fs.findLocalSiblingOrParent.mockResolvedValueOnce('uv.lock');
|
|
fs.readLocalFile.mockResolvedValueOnce('test content');
|
|
fs.readLocalFile.mockResolvedValueOnce('changed test content');
|
|
// python
|
|
getPkgReleases.mockResolvedValueOnce({
|
|
releases: [{ version: '3.11.1' }, { version: '3.11.2' }],
|
|
});
|
|
// uv
|
|
getPkgReleases.mockResolvedValueOnce({
|
|
releases: [{ version: '0.2.35' }, { version: '0.2.28' }],
|
|
});
|
|
|
|
const updatedDeps = [
|
|
{
|
|
packageName: 'dep1',
|
|
depType: depTypes.dependencies,
|
|
datasource: PypiDatasource.id,
|
|
registryUrls: ['https://foobar.com'],
|
|
},
|
|
{
|
|
packageName: 'dep2',
|
|
depType: depTypes.dependencies,
|
|
datasource: PypiDatasource.id,
|
|
registryUrls: ['https://example.com'],
|
|
},
|
|
{
|
|
packageName: 'dep3',
|
|
depType: depTypes.dependencies,
|
|
datasource: PypiDatasource.id,
|
|
registryUrls: ['invalidurl'],
|
|
},
|
|
{
|
|
packageName: 'dep4',
|
|
depType: depTypes.dependencies,
|
|
datasource: GithubTagsDatasource.id,
|
|
registryUrls: ['https://github.com'],
|
|
},
|
|
{
|
|
packageName: 'dep5',
|
|
depType: depTypes.dependencies,
|
|
datasource: PypiDatasource.id,
|
|
registryUrls: [
|
|
'https://someregion-python.pkg.dev/some-project/some-repo/',
|
|
],
|
|
},
|
|
{
|
|
packageName: 'dep6',
|
|
depType: depTypes.dependencies,
|
|
datasource: PypiDatasource.id,
|
|
registryUrls: ['https://pinned.com/simple'],
|
|
},
|
|
{
|
|
packageName: 'dep7',
|
|
depType: depTypes.dependencies,
|
|
datasource: PypiDatasource.id,
|
|
registryUrls: ['https://unnamed.com/simple'],
|
|
},
|
|
];
|
|
const pyproject = parsePyProject(codeBlock`
|
|
[tool.uv.sources]
|
|
dep6 = { index = "pinned-index" }
|
|
|
|
[[tool.uv.index]]
|
|
name = "pinned-index"
|
|
url = "https://pinned.com/simple"
|
|
default = false
|
|
explicit = true
|
|
|
|
[[tool.uv.index]]
|
|
url = "https://unnamed.com/simple"
|
|
default = false
|
|
explicit = true
|
|
`)!;
|
|
|
|
const result = await processor.updateArtifacts(
|
|
{
|
|
packageFileName: 'pyproject.toml',
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
updatedDeps,
|
|
},
|
|
pyproject,
|
|
);
|
|
expect(result).toEqual([
|
|
{
|
|
file: {
|
|
contents: 'changed test content',
|
|
path: 'uv.lock',
|
|
type: 'addition',
|
|
},
|
|
},
|
|
]);
|
|
expect(execSnapshots).toMatchObject([
|
|
{
|
|
cmd: 'uv lock --upgrade-package dep1 --upgrade-package dep2 --upgrade-package dep3 --upgrade-package dep4 --upgrade-package dep5 --upgrade-package dep6 --upgrade-package dep7',
|
|
options: {
|
|
env: {
|
|
GIT_CONFIG_COUNT: '6',
|
|
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/',
|
|
UV_EXTRA_INDEX_URL:
|
|
'https://foobar.com/ https://user:pass@example.com/ https://oauth2accesstoken:some-token@someregion-python.pkg.dev/some-project/some-repo/',
|
|
UV_INDEX_PINNED_INDEX_USERNAME: 'user',
|
|
UV_INDEX_PINNED_INDEX_PASSWORD: 'pass',
|
|
},
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('dont propagate uv.tool.index into UV_EXTRA_INDEX_URL', async () => {
|
|
const execSnapshots = mockExecAll();
|
|
GlobalConfig.set(adminConfig);
|
|
hostRules.add({
|
|
matchHost: 'https://example.com',
|
|
username: 'user',
|
|
password: 'pass',
|
|
});
|
|
hostRules.add({
|
|
matchHost: 'https://pinned.com/simple',
|
|
username: 'user',
|
|
password: 'pass',
|
|
});
|
|
hostRules.add({
|
|
matchHost: 'https://implicit.com/simple',
|
|
username: 'user',
|
|
password: 'pass',
|
|
});
|
|
googleAuth.mockImplementation(
|
|
// TODO: fix typing
|
|
vi.fn<any>(
|
|
class {
|
|
getAccessToken = vi.fn();
|
|
},
|
|
),
|
|
);
|
|
fs.findLocalSiblingOrParent.mockResolvedValueOnce('uv.lock');
|
|
fs.readLocalFile.mockResolvedValueOnce('test content');
|
|
fs.readLocalFile.mockResolvedValueOnce('changed test content');
|
|
// python
|
|
getPkgReleases.mockResolvedValueOnce({
|
|
releases: [{ version: '3.11.1' }, { version: '3.11.2' }],
|
|
});
|
|
// uv
|
|
getPkgReleases.mockResolvedValueOnce({
|
|
releases: [{ version: '0.2.35' }, { version: '0.2.28' }],
|
|
});
|
|
|
|
const updatedDeps = [
|
|
{
|
|
packageName: 'dep1',
|
|
depType: depTypes.dependencies,
|
|
datasource: PypiDatasource.id,
|
|
registryUrls: ['https://example.com/simple'],
|
|
},
|
|
{
|
|
packageName: 'dep2',
|
|
depType: depTypes.dependencies,
|
|
datasource: PypiDatasource.id,
|
|
registryUrls: ['https://pinned.com/simple'],
|
|
},
|
|
{
|
|
packageName: 'dep3',
|
|
depType: depTypes.dependencies,
|
|
datasource: PypiDatasource.id,
|
|
registryUrls: [
|
|
'https://implicit.com/simple',
|
|
'https://pypi.org/pypi/',
|
|
],
|
|
},
|
|
];
|
|
const pyproject = parsePyProject(codeBlock`
|
|
[tool.uv.sources]
|
|
dep2 = { index = "pinned-index" }
|
|
|
|
[[tool.uv.index]]
|
|
name = "pinned-index"
|
|
url = "https://pinned.com/simple"
|
|
default = false
|
|
explicit = true
|
|
|
|
[[tool.uv.index]]
|
|
name = "implicit-index"
|
|
url = "https://implicit.com/simple"
|
|
default = false
|
|
explicit = false
|
|
`)!;
|
|
|
|
const result = await processor.updateArtifacts(
|
|
{
|
|
packageFileName: 'pyproject.toml',
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
updatedDeps,
|
|
},
|
|
pyproject,
|
|
);
|
|
expect(result).toEqual([
|
|
{
|
|
file: {
|
|
contents: 'changed test content',
|
|
path: 'uv.lock',
|
|
type: 'addition',
|
|
},
|
|
},
|
|
]);
|
|
expect(execSnapshots).toMatchObject([
|
|
{
|
|
cmd: 'uv lock --upgrade-package dep1 --upgrade-package dep2 --upgrade-package dep3',
|
|
options: {
|
|
env: {
|
|
UV_EXTRA_INDEX_URL: 'https://user:pass@example.com/simple',
|
|
UV_INDEX_PINNED_INDEX_USERNAME: 'user',
|
|
UV_INDEX_PINNED_INDEX_PASSWORD: 'pass',
|
|
UV_INDEX_IMPLICIT_INDEX_USERNAME: 'user',
|
|
UV_INDEX_IMPLICIT_INDEX_PASSWORD: 'pass',
|
|
},
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('continues if Google auth is not configured', async () => {
|
|
const execSnapshots = mockExecAll();
|
|
GlobalConfig.set(adminConfig);
|
|
googleAuth.mockImplementation(
|
|
// TODO: fix typing
|
|
vi.fn<any>(
|
|
class {
|
|
getAccessToken = vi.fn();
|
|
},
|
|
),
|
|
);
|
|
fs.findLocalSiblingOrParent.mockResolvedValueOnce('uv.lock');
|
|
fs.readLocalFile.mockResolvedValueOnce('test content');
|
|
fs.readLocalFile.mockResolvedValueOnce('changed test content');
|
|
// python
|
|
getPkgReleases.mockResolvedValueOnce({
|
|
releases: [{ version: '3.11.1' }, { version: '3.11.2' }],
|
|
});
|
|
// uv
|
|
getPkgReleases.mockResolvedValueOnce({
|
|
releases: [{ version: '0.2.35' }, { version: '0.2.28' }],
|
|
});
|
|
|
|
const updatedDeps = [
|
|
{
|
|
packageName: 'dep',
|
|
depType: depTypes.dependencies,
|
|
datasource: PypiDatasource.id,
|
|
registryUrls: [
|
|
'https://someregion-python.pkg.dev/some-project/some-repo/simple/',
|
|
],
|
|
},
|
|
];
|
|
const result = await processor.updateArtifacts(
|
|
{
|
|
packageFileName: 'pyproject.toml',
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
updatedDeps,
|
|
},
|
|
parsePyProject('')!,
|
|
);
|
|
expect(result).toEqual([
|
|
{
|
|
file: {
|
|
contents: 'changed test content',
|
|
path: 'uv.lock',
|
|
type: 'addition',
|
|
},
|
|
},
|
|
]);
|
|
expect(execSnapshots).toMatchObject([
|
|
{
|
|
cmd: 'uv lock --upgrade-package dep',
|
|
options: {
|
|
env: {
|
|
UV_EXTRA_INDEX_URL:
|
|
'https://someregion-python.pkg.dev/some-project/some-repo/simple/',
|
|
},
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('return update on lockfileMaintenance', async () => {
|
|
const execSnapshots = mockExecAll();
|
|
GlobalConfig.set(adminConfig);
|
|
fs.findLocalSiblingOrParent.mockResolvedValueOnce('uv.lock');
|
|
fs.readLocalFile.mockResolvedValueOnce('test content');
|
|
fs.readLocalFile.mockResolvedValueOnce('changed test content');
|
|
// python
|
|
getPkgReleases.mockResolvedValueOnce({
|
|
releases: [{ version: '3.11.1' }, { version: '3.11.2' }],
|
|
});
|
|
// uv
|
|
getPkgReleases.mockResolvedValueOnce({
|
|
releases: [{ version: '0.2.35' }, { version: '0.2.28' }],
|
|
});
|
|
|
|
const result = await processor.updateArtifacts(
|
|
{
|
|
packageFileName: 'folder/pyproject.toml',
|
|
newPackageFileContent: '',
|
|
config: {
|
|
isLockFileMaintenance: true,
|
|
},
|
|
updatedDeps: [],
|
|
},
|
|
parsePyProject('')!,
|
|
);
|
|
expect(result).toEqual([
|
|
{
|
|
file: {
|
|
contents: 'changed test content',
|
|
path: 'uv.lock',
|
|
type: 'addition',
|
|
},
|
|
},
|
|
]);
|
|
expect(execSnapshots).toMatchObject([
|
|
{
|
|
cmd: 'uv lock --upgrade',
|
|
options: {
|
|
cwd: '/tmp/github/some/repo/folder',
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
});
|
|
});
|