import upath from 'upath'; import { mockDeep } from 'vitest-mock-extended'; import { envMock, mockExecAll } from '~test/exec-util.ts'; import { env, fs, git, partial } from '~test/util.ts'; import { GlobalConfig } from '../../../config/global.ts'; import type { InternalGlobalConfigOptions, RepoGlobalConfig, } from '../../../config/types.ts'; import * as docker from '../../../util/exec/docker/index.ts'; import type { StatusResult } from '../../../util/git/types.ts'; import * as _datasource from '../../datasource/index.ts'; import type { UpdateArtifactsConfig } from '../types.ts'; import { updateArtifacts } from './index.ts'; vi.mock('../../../util/exec/env.ts'); vi.mock('../../../util/fs/index.ts'); vi.mock('../../datasource/index.ts', () => mockDeep()); const datasource = vi.mocked(_datasource); delete process.env.CP_HOME_DIR; process.env.CONTAINERBASE = 'true'; 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'), dockerSidecarImage: 'ghcr.io/renovatebot/base-image', binarySource: 'global', }; describe('modules/manager/cocoapods/artifacts', () => { beforeEach(() => { env.getChildProcessEnv.mockReturnValue(envMock.basic); vi.spyOn(docker, 'removeDockerContainer').mockResolvedValue(); // can't be mocked docker.resetPrefetchedImages(); GlobalConfig.set(adminConfig); // ruby datasource.getPkgReleases.mockResolvedValue({ releases: [ { version: '2.7.4' }, { version: '3.0.0' }, { version: '3.1.0' }, ], }); }); afterEach(() => { GlobalConfig.reset(); }); it('returns null if no Podfile.lock found', async () => { const execSnapshots = mockExecAll(); await expect( updateArtifacts({ packageFileName: 'Podfile', updatedDeps: [{ depName: 'foo' }], newPackageFileContent: '', config, }), ).resolves.toBeNull(); expect(execSnapshots).toEqual([]); }); it('returns null if no updatedDeps were provided', async () => { const execSnapshots = mockExecAll(); await expect( updateArtifacts({ packageFileName: 'Podfile', updatedDeps: [], newPackageFileContent: '', config, }), ).resolves.toBeNull(); expect(execSnapshots).toEqual([]); }); it('returns null for invalid local directory', async () => { const execSnapshots = mockExecAll(); GlobalConfig.set({ localDir: '', }); await expect( updateArtifacts({ packageFileName: 'Podfile', updatedDeps: [{ depName: 'foo' }], newPackageFileContent: '', config: {}, }), ).resolves.toBeNull(); expect(execSnapshots).toEqual([]); }); it('returns null if updatedDeps is empty', async () => { const execSnapshots = mockExecAll(); await expect( updateArtifacts({ packageFileName: 'Podfile', updatedDeps: [], newPackageFileContent: '', config, }), ).resolves.toBeNull(); expect(execSnapshots).toEqual([]); }); it('returns null if unchanged', async () => { const execSnapshots = mockExecAll(); fs.findLocalSiblingOrParent.mockResolvedValueOnce('Podfile.lock'); fs.readLocalFile.mockResolvedValueOnce('Current Podfile'); git.getRepoStatus.mockResolvedValueOnce( partial({ modified: [], }), ); fs.findLocalSiblingOrParent.mockResolvedValueOnce('Podfile.lock'); fs.readLocalFile.mockResolvedValueOnce('Current Podfile'); await expect( updateArtifacts({ packageFileName: 'Podfile', updatedDeps: [{ depName: 'foo' }], newPackageFileContent: '', config, }), ).resolves.toBeNull(); expect(execSnapshots).toMatchObject([{ cmd: 'pod install' }]); }); it('returns updated Podfile', async () => { const execSnapshots = mockExecAll(); GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.getSiblingFileName.mockReturnValueOnce('Podfile.lock'); fs.findLocalSiblingOrParent.mockResolvedValueOnce('Podfile'); fs.readLocalFile.mockResolvedValueOnce('Old Podfile'); git.getRepoStatus.mockResolvedValueOnce( partial({ modified: ['Podfile.lock'], }), ); fs.findLocalSiblingOrParent.mockResolvedValueOnce('Podfile'); fs.readLocalFile.mockResolvedValueOnce('New Podfile'); await expect( updateArtifacts({ packageFileName: 'Podfile', updatedDeps: [{ depName: 'foo' }], newPackageFileContent: 'plugin "cocoapods-acknowledgements"', config, }), ).resolves.toMatchObject([{ file: { contents: 'New Podfile' } }]); expect(execSnapshots).toMatchObject([ { cmd: 'docker pull ghcr.io/renovatebot/base-image' }, { 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 ruby 3.1.0 && install-tool cocoapods 3.1.0 && gem install cocoapods-acknowledgements && pod install\'', }, ]); }); it('returns updated Podfile and Pods files', async () => { const execSnapshots = mockExecAll(); GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.getSiblingFileName.mockReturnValueOnce('Podfile.lock'); fs.findLocalSiblingOrParent.mockResolvedValueOnce('Podfile.lock'); fs.readLocalFile.mockResolvedValueOnce('Old Manifest.lock'); fs.findLocalSiblingOrParent.mockResolvedValueOnce('Podfile.lock'); fs.readLocalFile.mockResolvedValueOnce('New Podfile'); fs.findLocalSiblingOrParent.mockResolvedValueOnce('Pods/Manifest.lock'); fs.readLocalFile.mockResolvedValueOnce('Pods manifest'); git.getRepoStatus.mockResolvedValueOnce( partial({ not_added: ['Pods/New'], modified: ['Podfile.lock', 'Pods/Manifest.lock'], deleted: ['Pods/Deleted'], }), ); await expect( updateArtifacts({ packageFileName: 'Podfile', updatedDeps: [{ depName: 'foo' }], newPackageFileContent: '', config, }), ).resolves.toMatchObject([ { file: { type: 'addition', path: 'Podfile.lock' } }, { file: { type: 'addition', path: 'Pods/Manifest.lock' } }, { file: { type: 'addition', path: 'Pods/New' } }, { file: { type: 'deletion', path: 'Pods/Deleted' } }, ]); expect(execSnapshots).toMatchObject([ { cmd: 'docker pull ghcr.io/renovatebot/base-image' }, { 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 ruby 3.1.0 && install-tool cocoapods 3.1.0 && pod install\'', }, ]); }); it('catches write error', async () => { const execSnapshots = mockExecAll(); fs.getSiblingFileName.mockReturnValueOnce('Podfile.lock'); fs.findLocalSiblingOrParent.mockResolvedValueOnce('Podfile.lock'); fs.readLocalFile.mockResolvedValueOnce('Current Podfile'); fs.writeLocalFile.mockImplementationOnce(() => { throw new Error('not found'); }); await expect( updateArtifacts({ packageFileName: 'Podfile', updatedDeps: [{ depName: 'foo' }], newPackageFileContent: '', config, }), ).resolves.toEqual([ { artifactError: { fileName: 'Podfile.lock', stderr: 'not found' } }, ]); expect(execSnapshots).toBeEmpty(); }); it('returns pod exec error', async () => { const execSnapshots = mockExecAll(new Error('exec exception')); fs.getSiblingFileName.mockReturnValueOnce('Podfile.lock'); fs.findLocalSiblingOrParent.mockResolvedValueOnce('Podfile.lock'); fs.readLocalFile.mockResolvedValueOnce('Old Podfile.lock'); fs.outputCacheFile.mockResolvedValueOnce(); fs.findLocalSiblingOrParent.mockResolvedValueOnce('Podfile.lock'); fs.readLocalFile.mockResolvedValueOnce('Old Podfile.lock'); await expect( updateArtifacts({ packageFileName: 'Podfile', updatedDeps: [{ depName: 'foo' }], newPackageFileContent: '', config, }), ).resolves.toEqual([ { artifactError: { fileName: 'Podfile.lock', stderr: 'exec exception' } }, ]); expect(execSnapshots).toMatchObject([{ cmd: 'pod install' }]); }); it('dynamically selects Docker image tag', async () => { const execSnapshots = mockExecAll(); GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.findLocalSiblingOrParent.mockResolvedValueOnce('Podfile.lock'); fs.readLocalFile.mockResolvedValueOnce('COCOAPODS: 1.2.4'); fs.findLocalSiblingOrParent.mockResolvedValueOnce('Podfile.lock'); fs.readLocalFile.mockResolvedValueOnce('New Podfile'); git.getRepoStatus.mockResolvedValueOnce( partial({ modified: ['Podfile.lock'], }), ); await updateArtifacts({ packageFileName: 'Podfile', updatedDeps: [{ depName: 'foo' }], newPackageFileContent: '', config, }); expect(execSnapshots).toMatchObject([ { cmd: 'docker pull ghcr.io/renovatebot/base-image' }, { 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 ruby 3.1.0' + ' && ' + 'install-tool cocoapods 1.2.4' + ' && ' + 'pod install' + "'", }, ]); }); it('supports install mode', async () => { const execSnapshots = mockExecAll(); GlobalConfig.set({ ...adminConfig, binarySource: 'install' }); fs.findLocalSiblingOrParent.mockResolvedValueOnce('Podfile.lock'); fs.readLocalFile.mockResolvedValueOnce('COCOAPODS: 1.2.4'); fs.findLocalSiblingOrParent.mockResolvedValueOnce('Podfile.lock'); fs.readLocalFile.mockResolvedValueOnce('New Podfile'); git.getRepoStatus.mockResolvedValueOnce( partial({ modified: ['Podfile.lock'], }), ); await updateArtifacts({ packageFileName: 'Podfile', updatedDeps: [{ depName: 'foo' }], newPackageFileContent: '', config, }); expect(execSnapshots).toMatchObject([ { cmd: 'install-tool ruby 3.1.0' }, { cmd: 'install-tool cocoapods 1.2.4' }, { cmd: 'pod install' }, ]); }); });