Files
renovate/lib/modules/manager/pipenv/artifacts.spec.ts
Sebastian Poxhofer 05e1271376 test: use expect().resolves instead of awaiting inside expect (#45672)
Enables the vitest/prefer-expect-resolves oxlint rule and applies its
autofix across the spec suite.
2026-09-04 10:40:46 +00:00

1239 lines
36 KiB
TypeScript

import type { Stats } from 'node:fs';
import * as _fsExtra from 'fs-extra';
import upath from 'upath';
import type { MockInstance } from 'vitest';
import { mockDeep } from 'vitest-mock-extended';
import { envMock, mockExecAll } from '~test/exec-util.ts';
import { Fixtures } from '~test/fixtures.ts';
import { hostRules } from '~test/host-rules.ts';
import { env, git, partial } from '~test/util.ts';
import { GlobalConfig } from '../../../config/global.ts';
import type {
InternalGlobalConfigOptions,
RepoGlobalConfig,
} from '../../../config/types.ts';
import { logger } from '../../../logger/index.ts';
import * as docker from '../../../util/exec/docker/index.ts';
import type { ExtraEnv, Opt } from '../../../util/exec/types.ts';
import type { StatusResult } from '../../../util/git/types.ts';
import * as _datasource from '../../datasource/index.ts';
import type { UpdateArtifactsConfig } from '../types.ts';
import {
addExtraEnvVariable,
extractEnvironmentVariableName,
getMatchingHostRule,
} from './artifacts.ts';
import { updateArtifacts } from './index.ts';
import type { PipfileLock } from './types.ts';
// mock for cjs require for `@renovatebot/detect-tools`
// https://github.com/vitest-dev/vitest/discussions/3134
vi.hoisted(() => {
const fsExtraModule: Partial<NodeJS.Module> = {
exports: fixtures.fsExtra(),
};
require.cache[require.resolve('fs-extra')] = fsExtraModule as NodeJS.Module;
});
vi.mock('fs-extra', () => fixtures.fsExtra());
vi.mock('../../../util/exec/env.ts');
vi.mock('../../../util/git/index.ts', () => mockDeep());
vi.mock('../../../util/http/index.ts', () => mockDeep());
vi.mock('../../datasource/index.ts', () => mockDeep());
const datasource = vi.mocked(_datasource);
const fsExtra = vi.mocked(_fsExtra);
// vi.mocked() resolves stat() to its callback overload, so
// mockResolvedValueOnce() would expect void; retype via the promise overload
const statMock = fsExtra.stat as unknown as MockInstance<
(path: string) => Promise<Stats>
>;
process.env.CONTAINERBASE = 'true';
const adminConfig: RepoGlobalConfig & InternalGlobalConfigOptions = {
// `join` fixes Windows CI
localDir: upath.join(upath.join('/tmp/github/some/repo')),
cacheDir: upath.join(upath.join('/tmp/renovate/cache')),
containerbaseDir: upath.join(upath.join('/tmp/renovate/cache/containerbase')),
binarySource: 'global',
};
const dockerAdminConfig = {
...adminConfig,
binarySource: 'docker',
dockerSidecarImage: 'ghcr.io/renovatebot/base-image',
} satisfies RepoGlobalConfig & InternalGlobalConfigOptions;
const config: UpdateArtifactsConfig = {};
const lockMaintenanceConfig = { ...config, isLockFileMaintenance: true };
const pipenvCacheDir = upath.join('/tmp/renovate/cache/others/pipenv');
const pipCacheDir = upath.join('/tmp/renovate/cache/others/pip');
const virtualenvsCacheDir = upath.join(
'/tmp/renovate/cache/others/virtualenvs',
);
type MockFiles = Record<string, string | string[]>;
function mockFiles(mockFiles: MockFiles): void {
fsExtra.readFile.mockImplementation(((name: string) => {
for (const [key, value] of Object.entries(mockFiles)) {
if (name.endsWith(key)) {
if (!Array.isArray(value)) {
return value;
}
if (value.length > 1) {
return value.shift();
}
return value[0];
}
}
throw new Error('File not found');
}) as never);
}
describe('modules/manager/pipenv/artifacts', () => {
beforeEach(() => {
Fixtures.reset();
env.getChildProcessEnv.mockReturnValue({
...envMock.basic,
LANG: 'en_US.UTF-8',
LC_ALL: 'en_US',
});
GlobalConfig.set(adminConfig);
docker.resetPrefetchedImages();
// python
datasource.getPkgReleases.mockResolvedValueOnce({
releases: [
{ version: '3.6.2' },
{ version: '3.6.5' },
{ version: '3.7.6' },
{ version: '3.8.5' },
{ version: '3.9.1' },
{ version: '3.10.2' },
],
});
// pipenv
datasource.getPkgReleases.mockResolvedValueOnce({
releases: [
{ version: '2013.5.19' },
{ version: '2013.6.11' },
{ version: '2013.6.12' },
],
});
});
it('returns if no Pipfile.lock found', async () => {
await expect(
updateArtifacts({
packageFileName: 'Pipfile',
updatedDeps: [],
newPackageFileContent: '',
config,
}),
).resolves.toBeNull();
});
it('returns null if unchanged', async () => {
fsExtra.ensureDir.mockResolvedValue(undefined);
statMock.mockResolvedValueOnce(partial<Stats>());
mockFiles({
'/Pipfile.lock': JSON.stringify({
_meta: {
requires: { python_full_version: '3.7.6' },
},
} satisfies PipfileLock),
});
const execSnapshots = mockExecAll();
await expect(
updateArtifacts({
packageFileName: 'Pipfile',
updatedDeps: [],
newPackageFileContent: 'some new content',
config,
}),
).resolves.toBeNull();
expect(execSnapshots).toMatchObject([
{
cmd: 'pipenv lock',
options: {
cwd: upath.join('/tmp/github/some/repo'),
env: {
PIPENV_CACHE_DIR: pipenvCacheDir,
},
},
},
]);
expect(fsExtra.ensureDir.mock.calls).toEqual([
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/pipenv'))],
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/pip'))],
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/virtualenvs'))],
]);
expect(fsExtra.readFile.mock.calls).toEqual([
[expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile')), 'utf8'],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile.lock')),
'utf8',
],
[expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile')), 'utf8'],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile.lock')),
'utf8',
],
]);
});
it('gets python full version from Pipfile', async () => {
GlobalConfig.set({ ...adminConfig, binarySource: 'install' });
statMock.mockResolvedValueOnce(partial<Stats>());
mockFiles({
'/Pipfile': Fixtures.get('Pipfile1'),
'/Pipfile.lock': JSON.stringify({
_meta: {
requires: { python_full_version: '3.7.6' },
},
} satisfies PipfileLock),
});
fsExtra.ensureDir.mockResolvedValue(undefined);
const execSnapshots = mockExecAll();
await expect(
updateArtifacts({
packageFileName: 'Pipfile',
updatedDeps: [],
newPackageFileContent: Fixtures.get('Pipfile1'),
config,
}),
).resolves.toBeNull();
expect(execSnapshots).toMatchObject([
{ cmd: 'install-tool python 3.6.2' },
{},
{
cmd: 'pipenv lock',
options: {
cwd: upath.join('/tmp/github/some/repo'),
env: {
PIPENV_CACHE_DIR: pipenvCacheDir,
},
},
},
]);
expect(fsExtra.ensureDir.mock.calls).toEqual([
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/pipenv'))],
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/pip'))],
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/virtualenvs'))],
]);
expect(fsExtra.readFile.mock.calls).toEqual([
[expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile')), 'utf8'],
[expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile')), 'utf8'],
[expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile')), 'utf8'],
[expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile')), 'utf8'],
]);
});
it('gets python version from Pipfile', async () => {
GlobalConfig.set({ ...adminConfig, binarySource: 'install' });
statMock.mockResolvedValueOnce(partial<Stats>());
mockFiles({
'/Pipfile': Fixtures.get('Pipfile2'),
'/Pipfile.lock': JSON.stringify({
_meta: {
requires: { python_full_version: '3.7.6' },
},
} satisfies PipfileLock),
});
fsExtra.ensureDir.mockResolvedValue(undefined);
const execSnapshots = mockExecAll();
await expect(
updateArtifacts({
packageFileName: 'Pipfile',
updatedDeps: [],
newPackageFileContent: Fixtures.get('Pipfile2'),
config,
}),
).resolves.toBeNull();
expect(execSnapshots).toMatchObject([
{ cmd: 'install-tool python 3.6.5' },
{},
{
cmd: 'pipenv lock',
options: {
cwd: upath.join('/tmp/github/some/repo'),
env: {
PIPENV_CACHE_DIR: pipenvCacheDir,
},
},
},
]);
expect(fsExtra.ensureDir.mock.calls).toEqual([
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/pipenv'))],
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/pip'))],
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/virtualenvs'))],
]);
expect(fsExtra.readFile.mock.calls).toEqual([
[expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile')), 'utf8'],
[expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile')), 'utf8'],
[expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile')), 'utf8'],
[expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile')), 'utf8'],
]);
});
it('gets full python version from .python-version', async () => {
GlobalConfig.set({ ...adminConfig, binarySource: 'install' });
statMock.mockResolvedValueOnce(partial<Stats>());
mockFiles({
'/Pipfile.lock': '{}',
'/.python-version': '3.7.6',
});
fsExtra.ensureDir.mockResolvedValue(undefined);
const execSnapshots = mockExecAll();
await expect(
updateArtifacts({
packageFileName: 'Pipfile',
updatedDeps: [],
newPackageFileContent: 'some toml',
config,
}),
).resolves.toBeNull();
expect(execSnapshots).toMatchObject([
{ cmd: 'install-tool python 3.7.6' },
{},
{
cmd: 'pipenv lock',
options: {
cwd: upath.join('/tmp/github/some/repo'),
env: {
PIPENV_CACHE_DIR: pipenvCacheDir,
},
},
},
]);
expect(fsExtra.ensureDir.mock.calls).toEqual([
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/pipenv'))],
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/pip'))],
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/virtualenvs'))],
]);
expect(fsExtra.readFile.mock.calls).toEqual([
[expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile')), 'utf8'],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile.lock')),
'utf8',
],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/.python-version')),
'utf8',
],
[expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile')), 'utf8'],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile.lock')),
'utf8',
],
]);
});
it('gets python stream, from .python-version', async () => {
GlobalConfig.set({ ...adminConfig, binarySource: 'install' });
statMock.mockResolvedValueOnce(partial<Stats>());
fsExtra.ensureDir.mockResolvedValue(undefined);
mockFiles({
'/Pipfile.lock': '{}',
'/.python-version': '3.8',
});
const execSnapshots = mockExecAll();
await expect(
updateArtifacts({
packageFileName: 'Pipfile',
updatedDeps: [],
newPackageFileContent: 'some toml',
config,
}),
).resolves.toBeNull();
expect(execSnapshots).toMatchObject([
{ cmd: 'install-tool python 3.8.5' },
{},
{
cmd: 'pipenv lock',
options: {
cwd: upath.join('/tmp/github/some/repo'),
env: {
PIPENV_CACHE_DIR: pipenvCacheDir,
},
},
},
]);
expect(fsExtra.ensureDir.mock.calls).toEqual([
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/pipenv'))],
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/pip'))],
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/virtualenvs'))],
]);
expect(fsExtra.readFile.mock.calls).toEqual([
[expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile')), 'utf8'],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile.lock')),
'utf8',
],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/.python-version')),
'utf8',
],
[expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile')), 'utf8'],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile.lock')),
'utf8',
],
]);
});
it('handles no constraint', async () => {
fsExtra.ensureDir.mockResolvedValue(undefined);
statMock.mockResolvedValueOnce(partial<Stats>());
mockFiles({
'/Pipfile.lock': 'unparseable pipfile lock',
});
const execSnapshots = mockExecAll();
await expect(
updateArtifacts({
packageFileName: 'Pipfile',
updatedDeps: [],
newPackageFileContent: 'some new content',
config,
}),
).resolves.toBeNull();
expect(execSnapshots).toMatchObject([
{
cmd: 'pipenv lock',
options: {
cwd: upath.join('/tmp/github/some/repo'),
env: {
PIPENV_CACHE_DIR: pipenvCacheDir,
PIP_CACHE_DIR: pipCacheDir,
WORKON_HOME: virtualenvsCacheDir,
},
},
},
]);
expect(fsExtra.ensureDir.mock.calls).toEqual([
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/pipenv'))],
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/pip'))],
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/virtualenvs'))],
]);
expect(fsExtra.readFile.mock.calls).toEqual([
[expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile')), 'utf8'],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile.lock')),
'utf8',
],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/.python-version')),
'utf8',
],
[expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile')), 'utf8'],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile.lock')),
'utf8',
],
]);
});
it('returns updated Pipfile.lock', async () => {
fsExtra.ensureDir.mockResolvedValue(undefined);
statMock.mockResolvedValueOnce(partial<Stats>());
mockFiles({
'/Pipfile.lock': ['current pipfile.lock', 'new pipfile.lock'],
});
const execSnapshots = mockExecAll();
git.getRepoStatus.mockResolvedValue(
partial<StatusResult>({
modified: ['Pipfile.lock'],
}),
);
await expect(
updateArtifacts({
packageFileName: 'Pipfile',
updatedDeps: [],
newPackageFileContent: 'some new content',
config: { ...config, constraints: { python: '== 3.8.*' } },
}),
).resolves.toEqual([
{
file: {
contents: 'new pipfile.lock',
path: 'Pipfile.lock',
type: 'addition',
},
},
]);
expect(execSnapshots).toMatchObject([
{
cmd: 'pipenv lock',
options: {
cwd: upath.join('/tmp/github/some/repo'),
env: {
PIPENV_CACHE_DIR: pipenvCacheDir,
PIP_CACHE_DIR: pipCacheDir,
WORKON_HOME: virtualenvsCacheDir,
},
},
},
]);
expect(fsExtra.ensureDir.mock.calls).toEqual([
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/pipenv'))],
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/pip'))],
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/virtualenvs'))],
]);
expect(fsExtra.readFile.mock.calls).toEqual([
[expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile')), 'utf8'],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile.lock')),
'utf8',
],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile.lock')),
'utf8',
],
]);
});
it('supports docker mode', async () => {
GlobalConfig.set(dockerAdminConfig);
statMock.mockResolvedValueOnce(partial<Stats>());
const pipFileLock = JSON.stringify({
_meta: { requires: { python_version: '3.7' } },
} satisfies PipfileLock);
mockFiles({
'/Pipfile.lock': [pipFileLock, pipFileLock, 'new lock'],
});
fsExtra.ensureDir.mockResolvedValue(undefined);
// pipenv
datasource.getPkgReleases.mockResolvedValueOnce({
releases: [{ version: '2023.1.2' }],
});
const execSnapshots = mockExecAll();
git.getRepoStatus.mockResolvedValue(
partial<StatusResult>({
modified: ['Pipfile.lock'],
}),
);
await expect(
updateArtifacts({
packageFileName: 'Pipfile',
updatedDeps: [],
newPackageFileContent: 'some new content',
config,
}),
).resolves.not.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/renovate/cache":"/tmp/renovate/cache" ' +
'-e CI ' +
'-e PIPENV_CACHE_DIR ' +
'-e PIP_CACHE_DIR ' +
'-e WORKON_HOME ' +
'-e CONTAINERBASE_CACHE_DIR ' +
'-w "/tmp/github/some/repo" ' +
'ghcr.io/renovatebot/base-image' +
" bash -l -c '" +
'install-tool python 3.7.6' +
' && ' +
'install-tool pipenv 2013.6.12' +
' && ' +
'pipenv lock' +
"'",
options: {
cwd: upath.join('/tmp/github/some/repo'),
env: {
PIPENV_CACHE_DIR: pipenvCacheDir,
PIP_CACHE_DIR: pipCacheDir,
WORKON_HOME: virtualenvsCacheDir,
},
},
},
]);
expect(fsExtra.ensureDir.mock.calls).toEqual([
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/pipenv'))],
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/pip'))],
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/virtualenvs'))],
]);
expect(fsExtra.readFile.mock.calls).toEqual([
[expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile')), 'utf8'],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile.lock')),
'utf8',
],
[expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile')), 'utf8'],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile.lock')),
'utf8',
],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile.lock')),
'utf8',
],
]);
});
it('supports install mode', async () => {
GlobalConfig.set({ ...adminConfig, binarySource: 'install' });
statMock.mockResolvedValueOnce(partial<Stats>());
const pipFileLock = JSON.stringify({
_meta: { requires: { python_version: '3.6' } },
} satisfies PipfileLock);
mockFiles({
'/Pipfile.lock': [pipFileLock, 'new lock'],
});
fsExtra.ensureDir.mockResolvedValue(undefined);
// pipenv
datasource.getPkgReleases.mockResolvedValueOnce({
releases: [{ version: '2023.1.2' }],
});
const execSnapshots = mockExecAll();
git.getRepoStatus.mockResolvedValue(
partial<StatusResult>({
modified: ['Pipfile.lock'],
}),
);
await expect(
updateArtifacts({
packageFileName: 'Pipfile',
updatedDeps: [],
newPackageFileContent: 'some new content',
config,
}),
).resolves.not.toBeNull();
expect(execSnapshots).toMatchObject([
{ cmd: 'install-tool python 3.6.5' },
{ cmd: 'install-tool pipenv 2013.6.12' },
{
cmd: 'pipenv lock',
options: {
cwd: upath.join('/tmp/github/some/repo'),
env: {
PIPENV_CACHE_DIR: pipenvCacheDir,
PIP_CACHE_DIR: pipCacheDir,
WORKON_HOME: virtualenvsCacheDir,
},
},
},
]);
expect(fsExtra.ensureDir.mock.calls).toEqual([
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/pipenv'))],
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/pip'))],
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/virtualenvs'))],
]);
expect(fsExtra.readFile.mock.calls).toEqual([
[expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile')), 'utf8'],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile.lock')),
'utf8',
],
[expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile')), 'utf8'],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile.lock')),
'utf8',
],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile.lock')),
'utf8',
],
]);
});
it('defaults to latest if no lock constraints', async () => {
GlobalConfig.set({ ...adminConfig, binarySource: 'install' });
statMock.mockResolvedValueOnce(partial<Stats>());
fsExtra.ensureDir.mockResolvedValue(undefined);
mockFiles({
'/Pipfile.lock': ['{}', 'new lock'],
});
// pipenv
datasource.getPkgReleases.mockResolvedValueOnce({
releases: [{ version: '2023.1.2' }],
});
const execSnapshots = mockExecAll();
git.getRepoStatus.mockResolvedValue(
partial<StatusResult>({
modified: ['Pipfile.lock'],
}),
);
await expect(
updateArtifacts({
packageFileName: 'Pipfile',
updatedDeps: [],
newPackageFileContent: 'some new content',
config,
}),
).resolves.not.toBeNull();
expect(execSnapshots).toMatchObject([
{ cmd: 'install-tool python 3.10.2' },
{ cmd: 'install-tool pipenv 2013.6.12' },
{
cmd: 'pipenv lock',
options: {
cwd: upath.join('/tmp/github/some/repo'),
env: {
PIPENV_CACHE_DIR: pipenvCacheDir,
PIP_CACHE_DIR: pipCacheDir,
WORKON_HOME: virtualenvsCacheDir,
},
},
},
]);
expect(fsExtra.ensureDir.mock.calls).toEqual([
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/pipenv'))],
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/pip'))],
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/virtualenvs'))],
]);
expect(fsExtra.readFile.mock.calls).toEqual([
[expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile')), 'utf8'],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile.lock')),
'utf8',
],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/.python-version')),
'utf8',
],
[expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile')), 'utf8'],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile.lock')),
'utf8',
],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile.lock')),
'utf8',
],
]);
});
it('catches errors', async () => {
fsExtra.ensureDir.mockResolvedValue(undefined);
statMock.mockResolvedValueOnce(partial<Stats>());
mockFiles({
'/Pipfile.lock': 'Current Pipfile.lock',
});
fsExtra.outputFile.mockImplementationOnce(() => {
throw new Error('not found');
});
await expect(
updateArtifacts({
packageFileName: 'Pipfile',
updatedDeps: [],
newPackageFileContent: '{}',
config,
}),
).resolves.toEqual([
{ artifactError: { fileName: 'Pipfile.lock', stderr: 'not found' } },
]);
expect(fsExtra.ensureDir.mock.calls).toEqual([]);
expect(fsExtra.readFile.mock.calls).toEqual([]);
});
it('returns updated Pipenv.lock when doing lockfile maintenance', async () => {
fsExtra.ensureDir.mockResolvedValue(undefined);
statMock.mockResolvedValueOnce(partial<Stats>());
fsExtra.remove.mockResolvedValue();
mockFiles({
'/Pipfile.lock': ['Current Pipfile.lock', 'New Pipfile.lock'],
});
const execSnapshots = mockExecAll();
git.getRepoStatus.mockResolvedValue(
partial<StatusResult>({
modified: ['Pipfile.lock'],
}),
);
await expect(
updateArtifacts({
packageFileName: 'Pipfile',
updatedDeps: [],
newPackageFileContent: '{}',
config: lockMaintenanceConfig,
}),
).resolves.toEqual([
{
file: {
contents: 'New Pipfile.lock',
path: 'Pipfile.lock',
type: 'addition',
},
},
]);
expect(execSnapshots).toMatchObject([
{
cmd: 'pipenv lock',
options: {
cwd: upath.join('/tmp/github/some/repo'),
env: {
PIPENV_CACHE_DIR: pipenvCacheDir,
PIP_CACHE_DIR: pipCacheDir,
WORKON_HOME: virtualenvsCacheDir,
},
},
},
]);
});
it('uses pipenv version from Pipfile', async () => {
fsExtra.ensureDir.mockResolvedValue(undefined);
statMock.mockResolvedValueOnce(partial<Stats>());
GlobalConfig.set(dockerAdminConfig);
const oldLock = JSON.stringify({
default: { pipenv: { version: '==2020.8.13' } },
} satisfies PipfileLock);
mockFiles({
'/Pipfile.lock': [oldLock, oldLock, 'new lock'],
});
const execSnapshots = mockExecAll();
git.getRepoStatus.mockResolvedValue(
partial<StatusResult>({
modified: ['Pipfile.lock'],
}),
);
await expect(
updateArtifacts({
packageFileName: 'Pipfile',
updatedDeps: [],
newPackageFileContent: 'some new content',
config,
}),
).resolves.not.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/renovate/cache":"/tmp/renovate/cache" ' +
'-e CI ' +
'-e PIPENV_CACHE_DIR ' +
'-e PIP_CACHE_DIR ' +
'-e WORKON_HOME ' +
'-e CONTAINERBASE_CACHE_DIR ' +
'-w "/tmp/github/some/repo" ' +
'ghcr.io/renovatebot/base-image' +
" bash -l -c '" +
'install-tool python 3.10.2' +
' && ' +
'install-tool pipenv 2020.8.13' +
' && ' +
'pipenv lock' +
"'",
options: {
cwd: upath.join('/tmp/github/some/repo'),
env: {
PIPENV_CACHE_DIR: pipenvCacheDir,
WORKON_HOME: virtualenvsCacheDir,
},
},
},
]);
expect(fsExtra.ensureDir.mock.calls).toEqual([
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/pipenv'))],
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/pip'))],
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/virtualenvs'))],
]);
expect(fsExtra.readFile.mock.calls).toEqual([
[expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile')), 'utf8'],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile.lock')),
'utf8',
],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/.python-version')),
'utf8',
],
[expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile')), 'utf8'],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile.lock')),
'utf8',
],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile.lock')),
'utf8',
],
]);
});
it('uses pipenv version from Pipfile dev packages', async () => {
GlobalConfig.set(dockerAdminConfig);
statMock.mockResolvedValueOnce(partial<Stats>());
fsExtra.ensureDir.mockResolvedValue(undefined);
const oldLock = JSON.stringify({
develop: { pipenv: { version: '==2020.8.13' } },
} satisfies PipfileLock) as never;
mockFiles({
'/Pipfile.lock': [oldLock, oldLock, 'new lock'],
});
const execSnapshots = mockExecAll();
git.getRepoStatus.mockResolvedValue(
partial<StatusResult>({
modified: ['Pipfile.lock'],
}),
);
await expect(
updateArtifacts({
packageFileName: 'Pipfile',
updatedDeps: [],
newPackageFileContent: 'some new content',
config,
}),
).resolves.not.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/renovate/cache":"/tmp/renovate/cache" ' +
'-e CI ' +
'-e PIPENV_CACHE_DIR ' +
'-e PIP_CACHE_DIR ' +
'-e WORKON_HOME ' +
'-e CONTAINERBASE_CACHE_DIR ' +
'-w "/tmp/github/some/repo" ' +
'ghcr.io/renovatebot/base-image' +
" bash -l -c '" +
'install-tool python 3.10.2' +
' && ' +
'install-tool pipenv 2020.8.13' +
' && ' +
'pipenv lock' +
"'",
options: {
cwd: upath.join('/tmp/github/some/repo'),
env: {
PIPENV_CACHE_DIR: pipenvCacheDir,
WORKON_HOME: virtualenvsCacheDir,
},
},
},
]);
expect(fsExtra.ensureDir.mock.calls).toEqual([
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/pipenv'))],
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/pip'))],
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/virtualenvs'))],
]);
expect(fsExtra.readFile.mock.calls).toEqual([
[expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile')), 'utf8'],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile.lock')),
'utf8',
],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/.python-version')),
'utf8',
],
[expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile')), 'utf8'],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile.lock')),
'utf8',
],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile.lock')),
'utf8',
],
]);
});
it('uses pipenv version from config', async () => {
GlobalConfig.set(dockerAdminConfig);
statMock.mockResolvedValueOnce(partial<Stats>());
fsExtra.ensureDir.mockResolvedValue(undefined);
const oldLock = JSON.stringify({
default: { pipenv: { version: '==2020.8.13' } },
} satisfies PipfileLock) as never;
mockFiles({
'/Pipfile.lock': [oldLock, 'new lock'],
});
const execSnapshots = mockExecAll();
git.getRepoStatus.mockResolvedValue(
partial<StatusResult>({
modified: ['Pipfile.lock'],
}),
);
await expect(
updateArtifacts({
packageFileName: 'Pipfile',
updatedDeps: [],
newPackageFileContent: 'some new content',
config: { ...config, constraints: { pipenv: '==2020.1.1' } },
}),
).resolves.not.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/renovate/cache":"/tmp/renovate/cache" ' +
'-e CI ' +
'-e PIPENV_CACHE_DIR ' +
'-e PIP_CACHE_DIR ' +
'-e WORKON_HOME ' +
'-e CONTAINERBASE_CACHE_DIR ' +
'-w "/tmp/github/some/repo" ' +
'ghcr.io/renovatebot/base-image' +
" bash -l -c '" +
'install-tool python 3.10.2' +
' && ' +
'install-tool pipenv 2020.1.1' +
' && ' +
'pipenv lock' +
"'",
options: {
cwd: upath.join('/tmp/github/some/repo'),
env: {
PIPENV_CACHE_DIR: pipenvCacheDir,
WORKON_HOME: virtualenvsCacheDir,
},
},
},
]);
expect(fsExtra.ensureDir.mock.calls).toEqual([
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/pipenv'))],
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/pip'))],
[expect.toEndWith(upath.join('/tmp/renovate/cache/others/virtualenvs'))],
]);
expect(fsExtra.readFile.mock.calls).toEqual([
[expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile')), 'utf8'],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile.lock')),
'utf8',
],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/.python-version')),
'utf8',
],
[
expect.toEndWith(upath.join('/tmp/github/some/repo/Pipfile.lock')),
'utf8',
],
]);
});
it('passes private credential environment vars', async () => {
fsExtra.ensureDir.mockResolvedValue(undefined);
statMock.mockResolvedValueOnce(partial<Stats>());
mockFiles({
'/Pipfile.lock': ['current Pipfile.lock', 'New Pipfile.lock'],
});
const execSnapshots = mockExecAll();
git.getRepoStatus.mockResolvedValue(
partial<StatusResult>({
modified: ['Pipfile.lock'],
}),
);
hostRules.add({
matchHost: 'mypypi.example.com',
username: 'usernameOne',
password: 'passwordTwo',
});
await expect(
updateArtifacts({
packageFileName: 'Pipfile',
updatedDeps: [],
newPackageFileContent: Fixtures.get('Pipfile6'),
config: { ...config, constraints: { python: '== 3.8.*' } },
}),
).resolves.toEqual([
{
file: {
contents: 'New Pipfile.lock',
path: 'Pipfile.lock',
type: 'addition',
},
},
]);
expect(execSnapshots).toMatchObject([
{
cmd: 'pipenv lock',
options: {
cwd: upath.join('/tmp/github/some/repo'),
env: {
PIPENV_CACHE_DIR: pipenvCacheDir,
WORKON_HOME: virtualenvsCacheDir,
USERNAME: 'usernameOne',
PASSWORD: 'passwordTwo',
},
},
},
]);
});
it('returns no host rule on invalid url', () => {
expect(getMatchingHostRule('')).toBeNull();
});
it.each`
credential | result
${'$USERNAME'} | ${'USERNAME'}
${'$'} | ${null}
${''} | ${null}
${'${USERNAME}'} | ${'USERNAME'}
${'${USERNAME:-default}'} | ${'USERNAME'}
${'${COMPLEX_NAME_1:-default}'} | ${'COMPLEX_NAME_1'}
`('extractEnvironmentVariableName($credential)', ({ credential, result }) => {
expect(extractEnvironmentVariableName(credential)).toEqual(result);
});
it('warns about duplicate placeholders with different values', () => {
const extraEnv: Opt<ExtraEnv> = {
FOO: '1',
};
addExtraEnvVariable(extraEnv, 'FOO', '2');
expect(logger.warn).toHaveBeenCalledWith(
{
envVar: 'FOO',
},
'Possible misconfiguration, environment variable already set to a different value',
);
});
it('updates extraEnv if variable names differ from default', async () => {
fsExtra.ensureDir.mockResolvedValue(undefined);
statMock.mockResolvedValueOnce(partial<Stats>());
mockFiles({
'/Pipfile.lock': ['current Pipfile.lock', 'New Pipfile.lock'],
});
const execSnapshots = mockExecAll();
git.getRepoStatus.mockResolvedValue(
partial<StatusResult>({
modified: ['Pipfile.lock'],
}),
);
hostRules.add({
matchHost: 'mypypi.example.com',
username: 'usernameOne',
password: 'passwordTwo',
});
await expect(
updateArtifacts({
packageFileName: 'Pipfile',
updatedDeps: [],
newPackageFileContent: Fixtures.get('Pipfile7'),
config: { ...config, constraints: { python: '== 3.8.*' } },
}),
).resolves.toEqual([
{
file: {
contents: 'New Pipfile.lock',
path: 'Pipfile.lock',
type: 'addition',
},
},
]);
expect(execSnapshots).toMatchObject([
{
cmd: 'pipenv lock',
options: {
cwd: upath.join('/tmp/github/some/repo'),
env: {
PIPENV_CACHE_DIR: pipenvCacheDir,
WORKON_HOME: virtualenvsCacheDir,
USERNAME_FOO: 'usernameOne',
PAZZWORD: 'passwordTwo',
},
},
},
]);
});
});