Files
renovate/lib/modules/manager/deno/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

355 lines
12 KiB
TypeScript

import type { DirectoryResult } from 'tmp-promise';
import tmp from 'tmp-promise';
import { mockExecAll } from '~test/exec-util.ts';
import { fs } from '~test/util.ts';
import { GlobalConfig } from '../../../config/global.ts';
import { TEMPORARY_ERROR } from '../../../constants/error-messages.ts';
import { ExecError } from '../../../util/exec/exec-error.ts';
import * as hostRules from '../../../util/host-rules.ts';
import type { UpdateArtifact } from '../types.ts';
import { updateArtifacts } from './artifacts.ts';
vi.mock('../../../util/fs/index.ts');
const updateArtifact: UpdateArtifact = {
config: {
constraints: { deno: '2.4.5' },
},
newPackageFileContent: '',
packageFileName: '',
updatedDeps: [],
};
describe('modules/manager/deno/artifacts', () => {
beforeEach(() => {
hostRules.clear();
});
describe('updateArtifacts()', () => {
let localDirResult: DirectoryResult;
let localDir: string;
beforeEach(async () => {
localDirResult = await tmp.dir({ unsafeCleanup: true });
localDir = localDirResult.path;
GlobalConfig.set({ localDir, binarySource: 'global' });
});
afterEach(async () => {
await localDirResult?.cleanup();
});
it('skips if no updatedDeps and no lockFileMaintenance', async () => {
await expect(updateArtifacts(updateArtifact)).resolves.toBeNull();
});
it('skips if no lock file in config', async () => {
updateArtifact.updatedDeps = [{}];
await expect(updateArtifacts(updateArtifact)).resolves.toBeNull();
});
it('skips and returns an error if cannot read lock file', async () => {
updateArtifact.updatedDeps = [{ lockFiles: ['deno.lock'] }];
await expect(updateArtifacts(updateArtifact)).resolves.toEqual([
{
artifactError: {
fileName: 'deno.lock',
stderr: `Failed to read "deno.lock"`,
},
},
]);
});
it('returns null if lock content unchanged', async () => {
updateArtifact.updatedDeps = [{ lockFiles: ['deno.lock'] }];
const oldLock = Buffer.from('old');
fs.readLocalFile.mockResolvedValueOnce(oldLock as never);
// Second read is .npmrc
fs.readLocalFile.mockResolvedValueOnce(null);
fs.readLocalFile.mockResolvedValueOnce(oldLock as never);
mockExecAll();
await expect(updateArtifacts(updateArtifact)).resolves.toBeNull();
});
it('returns updated lock content', async () => {
updateArtifact.updatedDeps = [{ lockFiles: ['deno.lock'] }];
const oldLock = Buffer.from('old');
fs.readLocalFile.mockResolvedValueOnce(oldLock as never);
// Second read is .npmrc
fs.readLocalFile.mockResolvedValueOnce(null);
const newLock = Buffer.from('new');
fs.readLocalFile.mockResolvedValueOnce(newLock as never);
mockExecAll();
await expect(updateArtifacts(updateArtifact)).resolves.toEqual([
{
file: {
path: 'deno.lock',
type: 'addition',
contents: newLock,
},
},
]);
});
it('change directory if import map is used', async () => {
updateArtifact.updatedDeps = [
{
manager: 'deno',
lockFiles: ['sub/deno.lock'],
packageFile: 'import_map.json',
managerData: { importMapReferrer: 'sub/deno.json' },
},
];
const oldLock = Buffer.from('old');
fs.readLocalFile.mockResolvedValueOnce(oldLock as never);
// Second read is .npmrc
fs.readLocalFile.mockResolvedValueOnce(null);
const newLock = Buffer.from('new');
fs.readLocalFile.mockResolvedValueOnce(newLock as never);
mockExecAll();
await expect(updateArtifacts(updateArtifact)).resolves.toEqual([
{
file: {
path: 'sub/deno.lock',
type: 'addition',
contents: newLock,
},
},
]);
});
it('supports lockFileMaintenance', async () => {
updateArtifact.updatedDeps = [{ lockFiles: ['deno.lock'] }];
updateArtifact.config.updateType = 'lockFileMaintenance';
const oldLock = Buffer.from('old');
fs.readLocalFile.mockResolvedValueOnce(oldLock as never);
// Second read is .npmrc
fs.readLocalFile.mockResolvedValueOnce(null);
const newLock = Buffer.from('new');
fs.readLocalFile.mockResolvedValueOnce(newLock as never);
mockExecAll();
await expect(updateArtifacts(updateArtifact)).resolves.toEqual([
{
file: {
path: 'deno.lock',
type: 'addition',
contents: newLock,
},
},
]);
});
it('handles temporary error', async () => {
const execError = new ExecError(TEMPORARY_ERROR, {
cmd: '',
stdout: '',
stderr: '',
options: { encoding: 'utf8' },
});
updateArtifact.updatedDeps = [{ lockFiles: ['deno.lock'] }];
const oldLock = Buffer.from('old');
fs.readLocalFile.mockResolvedValueOnce(oldLock as never);
mockExecAll(execError);
await expect(updateArtifacts(updateArtifact)).rejects.toThrow(
TEMPORARY_ERROR,
);
});
it('handles full error', async () => {
const execError = new ExecError('nope', {
cmd: '',
stdout: '',
stderr: '',
options: { encoding: 'utf8' },
});
updateArtifact.updatedDeps = [{ lockFiles: ['deno.lock'] }];
const oldLock = Buffer.from('old');
fs.readLocalFile.mockResolvedValueOnce(oldLock as never);
mockExecAll(execError);
await expect(updateArtifacts(updateArtifact)).resolves.toEqual([
{ artifactError: { fileName: 'deno.lock', stderr: 'nope' } },
]);
});
});
it('depType tasks returns an error', async () => {
const updateArtifact: UpdateArtifact = {
config: {},
newPackageFileContent: '',
packageFileName: '',
updatedDeps: [
{ lockFiles: ['deno.lock'], depType: 'tasks', depName: 'dep1' },
],
};
const oldLock = Buffer.from('old');
fs.readLocalFile.mockResolvedValueOnce(oldLock as never);
// Second read is .npmrc
fs.readLocalFile.mockResolvedValueOnce(null);
const newLock = Buffer.from('new');
fs.readLocalFile.mockResolvedValueOnce(newLock as never);
await expect(updateArtifacts(updateArtifact)).resolves.toEqual([
{
artifactError: {
fileName: 'deno.lock',
stderr: `depType: "tasks", depName: "dep1" can't be updated with a lock file: "deno.lock"`,
},
},
]);
});
it('depType tasks.command returns an error', async () => {
const updateArtifact: UpdateArtifact = {
config: {},
newPackageFileContent: '',
packageFileName: '',
updatedDeps: [
{ lockFiles: ['deno.lock'], depType: 'tasks.command', depName: 'dep1' },
],
};
const oldLock = Buffer.from('old');
fs.readLocalFile.mockResolvedValueOnce(oldLock as never);
// Second read is .npmrc
fs.readLocalFile.mockResolvedValueOnce(null);
const newLock = Buffer.from('new');
fs.readLocalFile.mockResolvedValueOnce(newLock as never);
await expect(updateArtifacts(updateArtifact)).resolves.toEqual([
{
artifactError: {
fileName: 'deno.lock',
stderr: `depType: "tasks.command", depName: "dep1" can't be updated with a lock file: "deno.lock"`,
},
},
]);
});
it('supports lockFileMaintenance (without updated deps)', async () => {
updateArtifact.updatedDeps = [];
updateArtifact.config.lockFiles = ['deno.lock'];
updateArtifact.config.isLockFileMaintenance = true;
const oldLock = Buffer.from('old');
fs.readLocalFile.mockResolvedValueOnce(oldLock as never);
// Second read is .npmrc
fs.readLocalFile.mockResolvedValueOnce(null);
const newLock = Buffer.from('new');
fs.readLocalFile.mockResolvedValueOnce(newLock as never);
const execSnapshots = mockExecAll();
await expect(updateArtifacts(updateArtifact)).resolves.toEqual([
{
file: {
path: 'deno.lock',
type: 'addition',
contents: newLock,
},
},
]);
expect(execSnapshots).toMatchObject([
{
cmd: 'deno install --frozen=false',
},
]);
});
it('deno command execution', async () => {
const updateArtifact: UpdateArtifact = {
config: {},
newPackageFileContent: '',
packageFileName: '',
updatedDeps: [{ lockFiles: ['deno.lock'] }],
};
const oldLock = Buffer.from('old');
fs.readLocalFile.mockResolvedValueOnce(oldLock as never);
// Second read is .npmrc
fs.readLocalFile.mockResolvedValueOnce(null);
const newLock = Buffer.from('new');
fs.readLocalFile.mockResolvedValueOnce(newLock as never);
const execSnapshots = mockExecAll();
await updateArtifacts(updateArtifact);
expect(execSnapshots).toMatchObject([
{
cmd: 'deno install --frozen=false',
},
]);
});
describe('private registries', () => {
it('should add private registries to deno install command allow-import option', async () => {
const updateArtifact: UpdateArtifact = {
config: {
updateType: 'lockFileMaintenance',
lockFiles: ['deno.lock'],
},
newPackageFileContent: '',
packageFileName: '',
updatedDeps: [],
};
const oldLock = Buffer.from('old');
fs.readLocalFile.mockResolvedValueOnce(oldLock as never);
// Second read is .npmrc
fs.readLocalFile.mockResolvedValueOnce(null);
const newLock = Buffer.from('new');
fs.readLocalFile.mockResolvedValueOnce(newLock as never);
hostRules.add({
token: 'some-token',
hostType: 'npm',
matchHost: 'https://private-registry.example',
});
const execSnapshots = mockExecAll();
await expect(updateArtifacts(updateArtifact)).resolves.toEqual([
{
file: {
path: 'deno.lock',
type: 'addition',
contents: newLock,
},
},
]);
expect(execSnapshots).toMatchObject([
{
cmd: 'deno install --frozen=false --allow-import=deno.land:443,esm.sh:443,jsr.io:443,cdn.jsdelivr.net:443,raw.githubusercontent.com:443,gist.githubusercontent.com:443,private-registry.example',
},
]);
});
it('quotes the allow-import list when a hostRule resolvedHost contains shell metacharacters', async () => {
const updateArtifact: UpdateArtifact = {
config: {
updateType: 'lockFileMaintenance',
lockFiles: ['deno.lock'],
},
newPackageFileContent: '',
packageFileName: '',
updatedDeps: [],
};
const oldLock = Buffer.from('old');
fs.readLocalFile.mockResolvedValueOnce(oldLock as never);
// Second read is .npmrc
fs.readLocalFile.mockResolvedValueOnce(null);
const newLock = Buffer.from('new');
fs.readLocalFile.mockResolvedValueOnce(newLock as never);
// hostRules can be set via a repo's own committed config, not only by a
// trusted platform admin, so matchHost/resolvedHost aren't inherently
// trusted. A bare (schemeless) matchHost is preserved verbatim as
// resolvedHost when it doesn't parse as a URL.
hostRules.add({
token: 'some-token',
hostType: 'npm',
matchHost: 'x$(touch pwned)y',
});
const execSnapshots = mockExecAll();
await updateArtifacts(updateArtifact);
expect(execSnapshots).toMatchObject([
{
cmd: "deno install --frozen=false --allow-import='deno.land:443,esm.sh:443,jsr.io:443,cdn.jsdelivr.net:443,raw.githubusercontent.com:443,gist.githubusercontent.com:443,x$(touch pwned)y'",
},
]);
});
});
});