mirror of https://github.com/renovatebot/renovate
495 lines
17 KiB
TypeScript
495 lines
17 KiB
TypeScript
import { codeBlock } from 'common-tags';
|
|
import { Fixtures } from '../../../../test/fixtures';
|
|
import { fs } from '../../../../test/util';
|
|
import { GitRefsDatasource } from '../../datasource/git-refs';
|
|
import { GithubReleasesDatasource } from '../../datasource/github-releases';
|
|
import { GithubTagsDatasource } from '../../datasource/github-tags';
|
|
import { PypiDatasource } from '../../datasource/pypi';
|
|
import { extractPackageFile } from '.';
|
|
|
|
jest.mock('../../../util/fs');
|
|
|
|
const pyproject1toml = Fixtures.get('pyproject.1.toml');
|
|
const pyproject2toml = Fixtures.get('pyproject.2.toml');
|
|
const pyproject3toml = Fixtures.get('pyproject.3.toml');
|
|
const pyproject4toml = Fixtures.get('pyproject.4.toml');
|
|
const pyproject5toml = Fixtures.get('pyproject.5.toml');
|
|
const pyproject6toml = Fixtures.get('pyproject.6.toml');
|
|
const pyproject7toml = Fixtures.get('pyproject.7.toml');
|
|
const pyproject8toml = Fixtures.get('pyproject.8.toml');
|
|
const pyproject9toml = Fixtures.get('pyproject.9.toml');
|
|
|
|
// pyproject.10.toml use by artifacts
|
|
const pyproject11toml = Fixtures.get('pyproject.11.toml');
|
|
const pyproject11tomlLock = Fixtures.get('pyproject.11.toml.lock');
|
|
|
|
const pyproject12toml = Fixtures.get('pyproject.12.toml');
|
|
|
|
describe('modules/manager/poetry/extract', () => {
|
|
describe('extractPackageFile()', () => {
|
|
let filename: string;
|
|
const OLD_ENV = process.env;
|
|
|
|
beforeEach(() => {
|
|
filename = '';
|
|
process.env = { ...OLD_ENV };
|
|
delete process.env.PIP_INDEX_URL;
|
|
});
|
|
|
|
afterEach(() => {
|
|
process.env = OLD_ENV;
|
|
});
|
|
|
|
it('returns null for empty', async () => {
|
|
expect(await extractPackageFile('nothing here', filename)).toBeNull();
|
|
});
|
|
|
|
it('returns null for parsed file without poetry section', async () => {
|
|
expect(await extractPackageFile(pyproject5toml, filename)).toBeNull();
|
|
});
|
|
|
|
it('extracts multiple dependencies', async () => {
|
|
const res = await extractPackageFile(pyproject1toml, filename);
|
|
expect(res?.deps).toMatchSnapshot();
|
|
expect(res?.deps).toHaveLength(10);
|
|
expect(res?.extractedConstraints).toEqual({
|
|
python: '~2.7 || ^3.4',
|
|
});
|
|
});
|
|
|
|
it('extracts multiple dependencies (with dep = {version = "1.2.3"} case)', async () => {
|
|
const res = await extractPackageFile(pyproject2toml, filename);
|
|
expect(res).toMatchSnapshot();
|
|
expect(res?.deps).toHaveLength(8);
|
|
});
|
|
|
|
it('handles case with no dependencies', async () => {
|
|
const res = await extractPackageFile(pyproject3toml, filename);
|
|
expect(res).toBeNull();
|
|
});
|
|
|
|
it('handles multiple constraint dependencies', async () => {
|
|
const res = await extractPackageFile(pyproject4toml, filename);
|
|
expect(res).toMatchSnapshot();
|
|
expect(res?.deps).toHaveLength(1);
|
|
});
|
|
|
|
it('can parse TOML v1 heterogeneous arrays', async () => {
|
|
const res = await extractPackageFile(pyproject12toml, filename);
|
|
expect(res).not.toBeNull();
|
|
expect(res?.deps).toHaveLength(3);
|
|
});
|
|
|
|
it('extracts mixed versioning types', async () => {
|
|
const res = await extractPackageFile(pyproject9toml, filename);
|
|
expect(res).toMatchSnapshot({
|
|
deps: [
|
|
{ depName: 'dep1', currentValue: '0.2' },
|
|
{ depName: 'dep2', currentValue: '1.1.0' },
|
|
{ depName: 'dep3', currentValue: '1.0a1' },
|
|
{ depName: 'dep4', currentValue: '1.0b2' },
|
|
{ depName: 'dep5', currentValue: '1.0rc1' },
|
|
{ depName: 'dep6', currentValue: '1.0.dev4' },
|
|
{ depName: 'dep7', currentValue: '1.0c1' },
|
|
{ depName: 'dep8', currentValue: '2012.2' },
|
|
{ depName: 'dep9', currentValue: '1.0.dev456' },
|
|
{ depName: 'dep10', currentValue: '1.0a1' },
|
|
{ depName: 'dep11', currentValue: '1.0a2.dev456' },
|
|
{ depName: 'dep12', currentValue: '1.0a12.dev456' },
|
|
{ depName: 'dep13', currentValue: '1.0a12' },
|
|
{ depName: 'dep14', currentValue: '1.0b1.dev456' },
|
|
{ depName: 'dep15', currentValue: '1.0b2' },
|
|
{ depName: 'dep16', currentValue: '1.0b2.post345.dev456' },
|
|
{ depName: 'dep17', currentValue: '1.0b2.post345' },
|
|
{ depName: 'dep18', currentValue: '1.0rc1.dev456' },
|
|
{ depName: 'dep19', currentValue: '1.0rc1' },
|
|
{ depName: 'dep20', currentValue: '1.0' },
|
|
{ depName: 'dep21', currentValue: '1.0+abc.5' },
|
|
{ depName: 'dep22', currentValue: '1.0+abc.7' },
|
|
{ depName: 'dep23', currentValue: '1.0+5' },
|
|
{ depName: 'dep24', currentValue: '1.0.post456.dev34' },
|
|
{ depName: 'dep25', currentValue: '1.0.post456' },
|
|
{ depName: 'dep26', currentValue: '1.1.dev1' },
|
|
{ depName: 'dep27', currentValue: '~=3.1' },
|
|
{ depName: 'dep28', currentValue: '~=3.1.2' },
|
|
{ depName: 'dep29', currentValue: '~=3.1a1' },
|
|
{ depName: 'dep30', currentValue: '==3.1' },
|
|
{ depName: 'dep31', currentValue: '==3.1.*' },
|
|
{ depName: 'dep32', currentValue: '~=3.1.0, !=3.1.3' },
|
|
{ depName: 'dep33', currentValue: '<=2.0' },
|
|
{ depName: 'dep34', currentValue: '<2.0' },
|
|
],
|
|
});
|
|
});
|
|
|
|
it('extracts dependencies from dependency groups', async () => {
|
|
const content = codeBlock`
|
|
[tool.poetry.dependencies]
|
|
dep = "^2.0"
|
|
|
|
|
|
[tool.poetry.group.dev.dependencies]
|
|
dev_dep = "^3.0"
|
|
|
|
[tool.poetry.group.typing.dependencies]
|
|
typing_dep = "^4.0"
|
|
`;
|
|
const res = await extractPackageFile(content, filename);
|
|
expect(res?.deps).toMatchObject([
|
|
{
|
|
currentValue: '^2.0',
|
|
datasource: 'pypi',
|
|
depName: 'dep',
|
|
depType: 'dependencies',
|
|
},
|
|
{
|
|
currentValue: '^3.0',
|
|
datasource: 'pypi',
|
|
depName: 'dev_dep',
|
|
depType: 'dev',
|
|
packageName: 'dev-dep',
|
|
},
|
|
{
|
|
currentValue: '^4.0',
|
|
datasource: 'pypi',
|
|
depName: 'typing_dep',
|
|
depType: 'typing',
|
|
packageName: 'typing-dep',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('resolves lockedVersions from the lockfile', async () => {
|
|
fs.readLocalFile.mockResolvedValue(pyproject11tomlLock);
|
|
const res = await extractPackageFile(pyproject11toml, filename);
|
|
expect(res).toMatchSnapshot({
|
|
extractedConstraints: { python: '^3.9' },
|
|
deps: [
|
|
{ depName: 'python', currentValue: '^3.9' },
|
|
{ depName: 'boto3', lockedVersion: '1.17.5' },
|
|
],
|
|
});
|
|
});
|
|
|
|
it('parses git dependencies long commit hashes on http urls', async () => {
|
|
const content = codeBlock`
|
|
[tool.poetry.dependencies]
|
|
fastapi = {git = "https://github.com/tiangolo/fastapi.git", rev="6f5aa81c076d22e38afbe7d602db6730e28bc3cc"}
|
|
dep = "^2.0"
|
|
`;
|
|
const res = await extractPackageFile(content, filename);
|
|
expect(res?.deps).toMatchObject([
|
|
{
|
|
depType: 'dependencies',
|
|
depName: 'fastapi',
|
|
datasource: GitRefsDatasource.id,
|
|
currentDigest: '6f5aa81c076d22e38afbe7d602db6730e28bc3cc',
|
|
replaceString: '6f5aa81c076d22e38afbe7d602db6730e28bc3cc',
|
|
packageName: 'https://github.com/tiangolo/fastapi.git',
|
|
},
|
|
{
|
|
depType: 'dependencies',
|
|
depName: 'dep',
|
|
datasource: PypiDatasource.id,
|
|
currentValue: '^2.0',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('parses git dependencies short commit hashes on http urls', async () => {
|
|
const content = codeBlock`
|
|
[tool.poetry.dependencies]
|
|
fastapi = {git = "https://github.com/tiangolo/fastapi.git", rev="6f5aa81"}
|
|
dep = "^2.0"
|
|
`;
|
|
const res = await extractPackageFile(content, filename);
|
|
expect(res?.deps).toMatchObject([
|
|
{
|
|
depType: 'dependencies',
|
|
depName: 'fastapi',
|
|
datasource: GitRefsDatasource.id,
|
|
currentDigest: '6f5aa81',
|
|
replaceString: '6f5aa81',
|
|
packageName: 'https://github.com/tiangolo/fastapi.git',
|
|
},
|
|
{
|
|
depType: 'dependencies',
|
|
depName: 'dep',
|
|
datasource: PypiDatasource.id,
|
|
currentValue: '^2.0',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('parses git dependencies long commit hashes on ssh urls', async () => {
|
|
const content = codeBlock`
|
|
[tool.poetry.dependencies]
|
|
fastapi = {git = "git@github.com:tiangolo/fastapi.git", rev="6f5aa81c076d22e38afbe7d602db6730e28bc3cc"}
|
|
dep = "^2.0"
|
|
`;
|
|
const res = await extractPackageFile(content, filename);
|
|
expect(res?.deps).toMatchObject([
|
|
{
|
|
depType: 'dependencies',
|
|
depName: 'fastapi',
|
|
datasource: GitRefsDatasource.id,
|
|
currentDigest: '6f5aa81c076d22e38afbe7d602db6730e28bc3cc',
|
|
replaceString: '6f5aa81c076d22e38afbe7d602db6730e28bc3cc',
|
|
packageName: 'git@github.com:tiangolo/fastapi.git',
|
|
},
|
|
{
|
|
depType: 'dependencies',
|
|
depName: 'dep',
|
|
datasource: PypiDatasource.id,
|
|
currentValue: '^2.0',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('parses git dependencies long commit hashes on http urls with branch marker', async () => {
|
|
const content = codeBlock`
|
|
[tool.poetry.dependencies]
|
|
fastapi = {git = "https://github.com/tiangolo/fastapi.git", branch="develop", rev="6f5aa81c076d22e38afbe7d602db6730e28bc3cc"}
|
|
dep = "^2.0"
|
|
`;
|
|
const res = await extractPackageFile(content, filename);
|
|
expect(res?.deps).toMatchObject([
|
|
{
|
|
depType: 'dependencies',
|
|
depName: 'fastapi',
|
|
datasource: GitRefsDatasource.id,
|
|
currentValue: 'develop',
|
|
currentDigest: '6f5aa81c076d22e38afbe7d602db6730e28bc3cc',
|
|
replaceString: '6f5aa81c076d22e38afbe7d602db6730e28bc3cc',
|
|
packageName: 'https://github.com/tiangolo/fastapi.git',
|
|
},
|
|
{
|
|
depType: 'dependencies',
|
|
depName: 'dep',
|
|
datasource: PypiDatasource.id,
|
|
currentValue: '^2.0',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('parses github dependencies tags on ssh urls', async () => {
|
|
const content = codeBlock`
|
|
[tool.poetry.dependencies]
|
|
fastapi = {git = "git@github.com:tiangolo/fastapi.git", tag="1.2.3"}
|
|
werkzeug = ">=0.14"
|
|
`;
|
|
const res = (await extractPackageFile(content, filename))!.deps;
|
|
expect(res[0].depName).toBe('fastapi');
|
|
expect(res[0].packageName).toBe('tiangolo/fastapi');
|
|
expect(res[0].currentValue).toBe('1.2.3');
|
|
expect(res[0].skipReason).toBeUndefined();
|
|
expect(res[0].datasource).toBe(GithubTagsDatasource.id);
|
|
expect(res).toHaveLength(2);
|
|
});
|
|
|
|
it('parses github dependencies tags on http urls', async () => {
|
|
const content = codeBlock`
|
|
[tool.poetry.dependencies]
|
|
fastapi = {git = "https://github.com/tiangolo/fastapi.git", tag="1.2.3"}
|
|
werkzeug = ">=0.14"
|
|
`;
|
|
const res = (await extractPackageFile(content, filename))!.deps;
|
|
expect(res[0].depName).toBe('fastapi');
|
|
expect(res[0].packageName).toBe('tiangolo/fastapi');
|
|
expect(res[0].currentValue).toBe('1.2.3');
|
|
expect(res[0].skipReason).toBeUndefined();
|
|
expect(res[0].datasource).toBe(GithubTagsDatasource.id);
|
|
expect(res).toHaveLength(2);
|
|
});
|
|
|
|
it('parses git dependencies with tags that are not on GitHub', async () => {
|
|
const content = codeBlock`
|
|
[tool.poetry.dependencies]
|
|
aws-sam = {git = "https://gitlab.com/gitlab-examples/aws-sam.git", tag="1.2.3"}
|
|
platform-tools = {git = "https://some.company.com/platform-tools", tag="1.2.3"}
|
|
`;
|
|
const res = await extractPackageFile(content, filename);
|
|
expect(res?.deps).toMatchObject([
|
|
{
|
|
datasource: 'gitlab-tags',
|
|
depName: 'aws-sam',
|
|
packageName: 'gitlab-examples/aws-sam',
|
|
currentValue: '1.2.3',
|
|
},
|
|
{
|
|
datasource: 'git-tags',
|
|
depName: 'platform-tools',
|
|
packageName: 'https://some.company.com/platform-tools',
|
|
currentValue: '1.2.3',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('skips git dependencies', async () => {
|
|
const content = codeBlock`
|
|
[tool.poetry.dependencies]
|
|
flask = {git = "https://github.com/pallets/flask.git"}
|
|
werkzeug = ">=0.14"
|
|
`;
|
|
const res = (await extractPackageFile(content, filename))!.deps;
|
|
expect(res[0].depName).toBe('flask');
|
|
expect(res[0].skipReason).toBe('git-dependency');
|
|
expect(res).toHaveLength(2);
|
|
});
|
|
|
|
it('skips git dependencies with version', async () => {
|
|
const content = codeBlock`
|
|
[tool.poetry.dependencies]
|
|
flask = {git = "https://github.com/pallets/flask.git", version="1.2.3"}
|
|
werkzeug = ">=0.14"
|
|
`;
|
|
const res = (await extractPackageFile(content, filename))!.deps;
|
|
expect(res[0].depName).toBe('flask');
|
|
expect(res[0].currentValue).toBe('1.2.3');
|
|
expect(res[0].skipReason).toBe('git-dependency');
|
|
expect(res).toHaveLength(2);
|
|
});
|
|
|
|
it('skips path dependencies', async () => {
|
|
const content = codeBlock`
|
|
[tool.poetry.dependencies]
|
|
flask = {path = "/some/path/"}
|
|
werkzeug = ">=0.14"
|
|
`;
|
|
const res = (await extractPackageFile(content, filename))!.deps;
|
|
expect(res[0].depName).toBe('flask');
|
|
expect(res[0].skipReason).toBe('path-dependency');
|
|
expect(res).toHaveLength(2);
|
|
});
|
|
|
|
it('skips path dependencies with version', async () => {
|
|
const content = codeBlock`
|
|
[tool.poetry.dependencies]
|
|
flask = {path = "/some/path/", version = "1.2.3"}
|
|
werkzeug = ">=0.14"
|
|
`;
|
|
const res = (await extractPackageFile(content, filename))!.deps;
|
|
expect(res[0].depName).toBe('flask');
|
|
expect(res[0].currentValue).toBe('1.2.3');
|
|
expect(res[0].skipReason).toBe('path-dependency');
|
|
expect(res).toHaveLength(2);
|
|
});
|
|
|
|
it('does not include registry url for dependency python', async () => {
|
|
const content = codeBlock`
|
|
[tool.poetry.dependencies]
|
|
python = "^3.11"
|
|
|
|
[[tool.poetry.source]]
|
|
name = "custom-source"
|
|
url = "https://example.com"
|
|
priority = "explicit"
|
|
`;
|
|
const res = (await extractPackageFile(content, filename))!.deps;
|
|
expect(res).toHaveLength(1);
|
|
expect(res[0]).toMatchObject({
|
|
depName: 'python',
|
|
packageName: 'containerbase/python-prebuild',
|
|
currentValue: '^3.11',
|
|
datasource: GithubReleasesDatasource.id,
|
|
commitMessageTopic: 'Python',
|
|
registryUrls: null,
|
|
});
|
|
});
|
|
|
|
describe('registry URLs', () => {
|
|
it('can parse empty registries', async () => {
|
|
const res = await extractPackageFile(pyproject7toml, filename);
|
|
expect(res?.registryUrls).toBeUndefined();
|
|
});
|
|
|
|
it('can parse missing registries', async () => {
|
|
const res = await extractPackageFile(pyproject1toml, filename);
|
|
expect(res?.registryUrls).toBeUndefined();
|
|
});
|
|
|
|
it('extracts registries', async () => {
|
|
const res = await extractPackageFile(pyproject6toml, filename);
|
|
expect(res?.registryUrls).toMatchObject([
|
|
'https://foo.bar/simple/',
|
|
'https://bar.baz/+simple/',
|
|
'https://pypi.org/pypi/',
|
|
]);
|
|
});
|
|
|
|
it('dedupes registries', async () => {
|
|
const res = await extractPackageFile(pyproject8toml, filename);
|
|
expect(res?.registryUrls).toMatchObject([
|
|
'https://pypi.org/pypi/',
|
|
'https://bar.baz/+simple/',
|
|
]);
|
|
});
|
|
|
|
it('source with priority="default" and implicit PyPI priority="primary"', async () => {
|
|
const content = codeBlock`
|
|
[tool.poetry.dependencies]
|
|
python = "^3.11"
|
|
|
|
[[tool.poetry.source]]
|
|
name = "foo"
|
|
url = "https://foo.bar/simple/"
|
|
priority = "default"
|
|
|
|
[[tool.poetry.source]]
|
|
name = "PyPI"
|
|
`;
|
|
const res = await extractPackageFile(content, filename);
|
|
expect(res?.registryUrls).toMatchObject([
|
|
'https://foo.bar/simple/',
|
|
'https://pypi.org/pypi/',
|
|
]);
|
|
});
|
|
|
|
it('source with implicit priority and PyPI with priority="explicit"', async () => {
|
|
const content = codeBlock`
|
|
[tool.poetry.dependencies]
|
|
python = "^3.11"
|
|
|
|
[[tool.poetry.source]]
|
|
name = "foo"
|
|
url = "https://foo.bar/simple/"
|
|
|
|
[[tool.poetry.source]]
|
|
name = "PyPI"
|
|
priority = "explicit"
|
|
`;
|
|
const res = await extractPackageFile(content, filename);
|
|
expect(res?.registryUrls).toMatchObject(['https://foo.bar/simple/']);
|
|
});
|
|
|
|
it('supports dependencies with explicit source', async () => {
|
|
const content = codeBlock`
|
|
[tool.poetry.dependencies]
|
|
attrs = "^23.1.0"
|
|
typer = { version = "^0.9.0", source = "pypi" }
|
|
requests-cache = { version = "^1.1.0", source = "artifactory" }
|
|
|
|
[[tool.poetry.source]]
|
|
name = "artifactory"
|
|
url = "https://example.com"
|
|
priority = "explicit"
|
|
`;
|
|
const res = await extractPackageFile(content, filename);
|
|
expect(res?.deps).toMatchObject([
|
|
{ depName: 'attrs', currentValue: '^23.1.0' },
|
|
{
|
|
depName: 'typer',
|
|
currentValue: '^0.9.0',
|
|
registryUrls: ['https://pypi.org/pypi/'],
|
|
},
|
|
{
|
|
depName: 'requests-cache',
|
|
currentValue: '^1.1.0',
|
|
registryUrls: ['https://example.com'],
|
|
},
|
|
]);
|
|
});
|
|
});
|
|
});
|
|
});
|