import upath from 'upath'; import { envMock, mockExecAll } from '~test/exec-util.ts'; import { env, fs, git, scm } 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 * as docker from '../../../util/exec/docker/index.ts'; import { nugetOrg } from '../../datasource/nuget/index.ts'; import type { UpdateArtifactsConfig } from '../types.ts'; import * as nuget from './index.ts'; import * as util from './util.ts'; vi.mock('../../../util/exec/env.ts'); vi.mock('../../../util/fs/index.ts'); vi.mock('./util.ts'); const { getDefaultRegistries, findGlobalJson } = vi.mocked(util); process.env.CONTAINERBASE = 'true'; const adminConfig: RepoGlobalConfig & InternalGlobalConfigOptions = { // `join` fixes Windows CI localDir: upath.join('/tmp/github/some/repo'), cacheDir: upath.join('/tmp/renovate/cache'), containerbaseDir: upath.join('/tmp/renovate/cache/containerbase'), binarySource: 'global', }; const config: UpdateArtifactsConfig = {}; describe('modules/manager/nuget/artifacts', () => { beforeEach(async () => { const realFs = await vi.importActual< typeof import('../../../util/fs/index.ts') >('../../../util/fs/index.ts'); getDefaultRegistries.mockReturnValue([ { url: nugetOrg, name: 'nuget.org' }, ]); env.getChildProcessEnv.mockReturnValue(envMock.basic); fs.privateCacheDir.mockImplementation(realFs.privateCacheDir); scm.getFileList.mockResolvedValueOnce([]); GlobalConfig.set(adminConfig); docker.resetPrefetchedImages(); }); afterEach(() => { GlobalConfig.reset(); }); it('re-throws TEMPORARY_ERROR', async () => { fs.getSiblingFileName.mockReturnValueOnce('packages.lock.json'); git.getFiles.mockResolvedValueOnce({ 'packages.lock.json': 'Current packages.lock.json', }); fs.getLocalFiles.mockResolvedValueOnce({ 'packages.lock.json': 'New packages.lock.json', }); fs.writeLocalFile.mockRejectedValueOnce(new Error(TEMPORARY_ERROR)); await expect( nuget.updateArtifacts({ packageFileName: 'project.csproj', updatedDeps: [{ depName: 'dep' }], newPackageFileContent: '{}', config, }), ).rejects.toThrow(TEMPORARY_ERROR); }); it('aborts if no lock file found', async () => { const execSnapshots = mockExecAll(); fs.getSiblingFileName.mockReturnValueOnce('packages.lock.json'); git.getFiles.mockResolvedValueOnce({ 'packages.lock.json': null }); await expect( nuget.updateArtifacts({ packageFileName: 'project.csproj', updatedDeps: [{ depName: 'foo' }, { depName: 'bar' }], newPackageFileContent: '{}', config, }), ).resolves.toBeNull(); expect(execSnapshots).toBeEmptyArray(); }); it('aborts if lock file is unchanged', async () => { const execSnapshots = mockExecAll(); fs.getSiblingFileName.mockReturnValueOnce( 'path/with space/packages.lock.json', ); git.getFiles.mockResolvedValueOnce({ 'path/with space/packages.lock.json': 'Current packages.lock.json', }); fs.getLocalFiles.mockResolvedValueOnce({ 'path/with space/packages.lock.json': 'Current packages.lock.json', }); await expect( nuget.updateArtifacts({ packageFileName: 'path/with space/project.csproj', updatedDeps: [{ depName: 'foo' }, { depName: 'bar' }], newPackageFileContent: '{}', config, }), ).resolves.toBeNull(); expect(execSnapshots).toMatchObject([ { cmd: "dotnet restore 'path/with space/project.csproj' --force-evaluate --configfile /tmp/renovate/cache/__renovate-private-cache/nuget/nuget.config", options: { cwd: '/tmp/github/some/repo', env: { NUGET_PACKAGES: '/tmp/renovate/cache/__renovate-private-cache/nuget/packages', MSBUILDDISABLENODEREUSE: '1', }, }, }, ]); }); it('runs workload restore and updates lock file', async () => { const execSnapshots = mockExecAll(); fs.getSiblingFileName.mockReturnValueOnce('packages.lock.json'); git.getFiles.mockResolvedValueOnce({ 'packages.lock.json': 'Current packages.lock.json', }); fs.getLocalFiles.mockResolvedValueOnce({ 'packages.lock.json': 'New packages.lock.json', }); await expect( nuget.updateArtifacts({ packageFileName: 'project.csproj', updatedDeps: [ { depName: 'dep', registryUrls: ['https://contoso.com/packages/'] }, ], newPackageFileContent: '{}', config: { ...config, postUpdateOptions: ['dotnetWorkloadRestore'] }, }), ).resolves.toEqual([ { file: { contents: 'New packages.lock.json', path: 'packages.lock.json', type: 'addition', }, }, ]); expect(execSnapshots).toMatchObject([ { cmd: 'dotnet workload restore --configfile /tmp/renovate/cache/__renovate-private-cache/nuget/nuget.config', options: { cwd: '/tmp/github/some/repo', env: { NUGET_PACKAGES: '/tmp/renovate/cache/__renovate-private-cache/nuget/packages', MSBUILDDISABLENODEREUSE: '1', }, }, }, { cmd: 'dotnet restore project.csproj --force-evaluate --configfile /tmp/renovate/cache/__renovate-private-cache/nuget/nuget.config', options: { cwd: '/tmp/github/some/repo', env: { NUGET_PACKAGES: '/tmp/renovate/cache/__renovate-private-cache/nuget/packages', MSBUILDDISABLENODEREUSE: '1', }, }, }, ]); }); it('does not update lock file when non-proj file is changed', async () => { const execSnapshots = mockExecAll(); fs.getSiblingFileName.mockReturnValueOnce('packages.lock.json'); git.getFiles.mockResolvedValueOnce({ 'packages.lock.json': 'Current packages.lock.json', }); fs.getLocalFiles.mockResolvedValueOnce({ 'packages.lock.json': 'New packages.lock.json', }); await expect( nuget.updateArtifacts({ packageFileName: 'otherfile.props', updatedDeps: [{ depName: 'dep' }], newPackageFileContent: '{}', config, }), ).resolves.toBeNull(); expect(execSnapshots).toBeEmptyArray(); }); it('updates lock file for Directory.Build.props', async () => { const execSnapshots = mockExecAll(); scm.getFileList.mockReset(); scm.getFileList.mockResolvedValueOnce(['project.csproj']); fs.getSiblingFileName .mockReturnValueOnce('packages.lock.json') .mockReturnValueOnce('packages.lock.json'); git.getFiles.mockResolvedValueOnce({ 'packages.lock.json': 'Current packages.lock.json', }); fs.getLocalFiles.mockResolvedValueOnce({ 'packages.lock.json': 'New packages.lock.json', }); await expect( nuget.updateArtifacts({ packageFileName: 'Directory.Build.props', updatedDeps: [{ depName: 'dep' }], newPackageFileContent: '{}', config, }), ).resolves.toEqual([ { file: { contents: 'New packages.lock.json', path: 'packages.lock.json', type: 'addition', }, }, ]); expect(execSnapshots).toMatchObject([ { cmd: 'dotnet restore project.csproj --force-evaluate --configfile /tmp/renovate/cache/__renovate-private-cache/nuget/nuget.config', }, ]); }); it('updates lock file for nested Directory.Build.props', async () => { const execSnapshots = mockExecAll(); scm.getFileList.mockReset(); scm.getFileList.mockResolvedValueOnce(['src/project.csproj']); fs.getSiblingFileName .mockReturnValueOnce('src/packages.lock.json') .mockReturnValueOnce('src/packages.lock.json'); git.getFiles.mockResolvedValueOnce({ 'src/packages.lock.json': 'Current packages.lock.json', }); fs.getLocalFiles.mockResolvedValueOnce({ 'src/packages.lock.json': 'New packages.lock.json', }); await expect( nuget.updateArtifacts({ packageFileName: 'src/Directory.Build.props', updatedDeps: [{ depName: 'dep' }], newPackageFileContent: '{}', config, }), ).resolves.toEqual([ { file: { contents: 'New packages.lock.json', path: 'src/packages.lock.json', type: 'addition', }, }, ]); expect(execSnapshots).toMatchObject([ { cmd: 'dotnet restore src/project.csproj --force-evaluate --configfile /tmp/renovate/cache/__renovate-private-cache/nuget/nuget.config', }, ]); }); it('does not update lock file when no deps changed', async () => { const execSnapshots = mockExecAll(); fs.getSiblingFileName.mockReturnValueOnce('packages.lock.json'); git.getFiles.mockResolvedValueOnce({ 'packages.lock.json': 'Current packages.lock.json', }); fs.getLocalFiles.mockResolvedValueOnce({ 'packages.lock.json': 'New packages.lock.json', }); await expect( nuget.updateArtifacts({ packageFileName: 'project.csproj', updatedDeps: [], newPackageFileContent: '{}', config, }), ).resolves.toBeNull(); expect(execSnapshots).toBeEmptyArray(); }); it('performs lock file maintenance', async () => { const execSnapshots = mockExecAll(); fs.getSiblingFileName.mockReturnValueOnce('packages.lock.json'); git.getFiles.mockResolvedValueOnce({ 'packages.lock.json': 'Current packages.lock.json', }); fs.getLocalFiles.mockResolvedValueOnce({ 'packages.lock.json': 'New packages.lock.json', }); await expect( nuget.updateArtifacts({ packageFileName: 'project.csproj', updatedDeps: [], newPackageFileContent: '{}', config: { ...config, isLockFileMaintenance: true, }, }), ).resolves.toEqual([ { file: { contents: 'New packages.lock.json', path: 'packages.lock.json', type: 'addition', }, }, ]); expect(execSnapshots).toMatchObject([ { cmd: 'dotnet restore project.csproj --force-evaluate --configfile /tmp/renovate/cache/__renovate-private-cache/nuget/nuget.config', options: { cwd: '/tmp/github/some/repo', env: { NUGET_PACKAGES: '/tmp/renovate/cache/__renovate-private-cache/nuget/packages', MSBUILDDISABLENODEREUSE: '1', }, }, }, ]); }); it('supports docker mode', async () => { GlobalConfig.set({ ...adminConfig, binarySource: 'docker', dockerSidecarImage: 'ghcr.io/renovatebot/base-image', }); const execSnapshots = mockExecAll(); fs.getSiblingFileName.mockReturnValueOnce('packages.lock.json'); git.getFiles.mockResolvedValueOnce({ 'packages.lock.json': 'Current packages.lock.json', }); fs.getLocalFiles.mockResolvedValueOnce({ 'packages.lock.json': 'New packages.lock.json', }); findGlobalJson.mockResolvedValueOnce({ sdk: { version: '7.0.100' } }); await expect( nuget.updateArtifacts({ packageFileName: 'project.csproj', updatedDeps: [{ depName: 'dep' }], newPackageFileContent: '{}', config, }), ).resolves.toEqual([ { file: { contents: 'New packages.lock.json', path: 'packages.lock.json', type: 'addition', }, }, ]); 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 NUGET_PACKAGES ' + '-e MSBUILDDISABLENODEREUSE ' + '-e CONTAINERBASE_CACHE_DIR ' + '-w "/tmp/github/some/repo" ' + 'ghcr.io/renovatebot/base-image ' + "bash -l -c '" + 'install-tool dotnet 7.0.100' + ' && ' + 'dotnet restore project.csproj --force-evaluate --configfile /tmp/renovate/cache/__renovate-private-cache/nuget/nuget.config' + "'", options: { env: { CONTAINERBASE_CACHE_DIR: '/tmp/renovate/cache/containerbase', NUGET_PACKAGES: '/tmp/renovate/cache/__renovate-private-cache/nuget/packages', MSBUILDDISABLENODEREUSE: '1', }, }, }, ]); }); it('supports install mode', async () => { GlobalConfig.set({ ...adminConfig, binarySource: 'install' }); const execSnapshots = mockExecAll(); fs.getSiblingFileName.mockReturnValueOnce('packages.lock.json'); git.getFiles.mockResolvedValueOnce({ 'packages.lock.json': 'Current packages.lock.json', }); fs.getLocalFiles.mockResolvedValueOnce({ 'packages.lock.json': 'New packages.lock.json', }); await expect( nuget.updateArtifacts({ packageFileName: 'project.csproj', updatedDeps: [{ depName: 'dep' }], newPackageFileContent: '{}', config: { ...config, constraints: { dotnet: '7.0.100' } }, }), ).resolves.toEqual([ { file: { contents: 'New packages.lock.json', path: 'packages.lock.json', type: 'addition', }, }, ]); expect(execSnapshots).toMatchObject([ { cmd: 'install-tool dotnet 7.0.100', options: { cwd: '/tmp/github/some/repo', env: { CONTAINERBASE_CACHE_DIR: '/tmp/renovate/cache/containerbase', NUGET_PACKAGES: '/tmp/renovate/cache/__renovate-private-cache/nuget/packages', MSBUILDDISABLENODEREUSE: '1', }, }, }, { cmd: 'dotnet restore project.csproj --force-evaluate --configfile /tmp/renovate/cache/__renovate-private-cache/nuget/nuget.config', options: { cwd: '/tmp/github/some/repo', env: { CONTAINERBASE_CACHE_DIR: '/tmp/renovate/cache/containerbase', NUGET_PACKAGES: '/tmp/renovate/cache/__renovate-private-cache/nuget/packages', MSBUILDDISABLENODEREUSE: '1', }, }, }, ]); }); it('supports global mode', async () => { GlobalConfig.set({ ...adminConfig, binarySource: 'global' }); const execSnapshots = mockExecAll(); fs.getSiblingFileName.mockReturnValueOnce('packages.lock.json'); git.getFiles.mockResolvedValueOnce({ 'packages.lock.json': 'Current packages.lock.json', }); fs.getLocalFiles.mockResolvedValueOnce({ 'packages.lock.json': 'New packages.lock.json', }); await expect( nuget.updateArtifacts({ packageFileName: 'project.csproj', updatedDeps: [{ depName: 'dep' }], newPackageFileContent: '{}', config, }), ).resolves.toEqual([ { file: { contents: 'New packages.lock.json', path: 'packages.lock.json', type: 'addition', }, }, ]); expect(execSnapshots).toMatchObject([ { cmd: 'dotnet restore project.csproj --force-evaluate --configfile /tmp/renovate/cache/__renovate-private-cache/nuget/nuget.config', options: { cwd: '/tmp/github/some/repo', env: { NUGET_PACKAGES: '/tmp/renovate/cache/__renovate-private-cache/nuget/packages', MSBUILDDISABLENODEREUSE: '1', }, }, }, ]); }); it('catches errors', async () => { const execSnapshots = mockExecAll(); fs.getSiblingFileName.mockReturnValueOnce('packages.lock.json'); git.getFiles.mockResolvedValueOnce({ 'packages.lock.json': 'Current packages.lock.json', }); fs.writeLocalFile.mockImplementationOnce(() => { throw new Error('not found'); }); await expect( nuget.updateArtifacts({ packageFileName: 'project.csproj', updatedDeps: [{ depName: 'dep' }], newPackageFileContent: '{}', config, }), ).resolves.toEqual([ { artifactError: { fileName: 'packages.lock.json', stderr: 'not found', }, }, ]); expect(execSnapshots).toBeEmptyArray(); }); });