From 092b7241bc9f30bc47f10fab1bea784deb9f96f3 Mon Sep 17 00:00:00 2001 From: Michael Kriese Date: Thu, 1 Oct 2026 13:50:14 +0200 Subject: [PATCH] test: cover modern services winning over same-named v2 tools on install Also fix the install container JSDoc and keep the prepare test from leaving a ruby prepared marker behind. Co-Authored-By: Claude Opus 5.5 --- src/cli/install-tool/index.spec.ts | 25 +++++++++++++++++++++++++ src/cli/install-tool/index.ts | 3 ++- src/cli/prepare-tool/index.spec.ts | 2 ++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/cli/install-tool/index.spec.ts b/src/cli/install-tool/index.spec.ts index 2e4b20d6ce..0f4aaa5e77 100644 --- a/src/cli/install-tool/index.spec.ts +++ b/src/cli/install-tool/index.spec.ts @@ -9,6 +9,7 @@ import { NpmVersionResolver } from '../tools/node/resolver.ts'; import { NpmBaseInstallService } from '../tools/node/utils.ts'; import { PipVersionResolver } from '../tools/python/pip.ts'; import { PipBaseInstallService } from '../tools/python/utils.ts'; +import { RubyInstallService } from '../tools/ruby/index.ts'; import { RubyBaseInstallService, RubyGemVersionResolver, @@ -21,6 +22,7 @@ import { resolveVersion, uninstallTool, } from './index.ts'; +import { V2ToolInstallService } from './install-legacy-tool.service.ts'; import { ensurePaths, rootPath } from '~test/path.ts'; vi.mock('del'); @@ -119,6 +121,29 @@ describe('cli/install-tool/index', () => { ).toBeUndefined(); }); + test('prefers a modern service over a v2 shell tool with the same name', async () => { + const script = rootPath('usr/local/containerbase/tools/v2/ruby.sh'); + await fs.writeFile(script, ''); + const proto = RubyInstallService.prototype; + vi.spyOn(proto, 'needsPrepare').mockReturnValue(false); + vi.spyOn(proto, 'needsInitialize').mockReturnValue(false); + vi.spyOn(proto, 'validate').mockResolvedValue(true); + vi.spyOn(proto, 'link').mockResolvedValue(); + vi.spyOn(proto, 'postInstall').mockResolvedValue(); + vi.spyOn(proto, 'test').mockResolvedValue(); + const ruby = vi.spyOn(proto, 'install').mockResolvedValue(); + const v2 = vi.spyOn(V2ToolInstallService.prototype, 'install'); + + try { + expect(await installTool('ruby', '3.4.11')).toBeUndefined(); + } finally { + await fs.rm(script); + } + + expect(ruby).toHaveBeenCalledExactlyOnceWith('3.4.11'); + expect(v2).not.toHaveBeenCalled(); + }); + test('rethrows a failing test for a known pip tool', async () => { // unlike the `dummy-*` tools above, `poetry` is in the `ResolverMap`, so // its `--version` flag is expected to work diff --git a/src/cli/install-tool/index.ts b/src/cli/install-tool/index.ts index 78213d1d51..32609b9ae3 100644 --- a/src/cli/install-tool/index.ts +++ b/src/cli/install-tool/index.ts @@ -127,7 +127,8 @@ import { TOOL_VERSION_RESOLVER } from './tool-version-resolver.ts'; /** * Creates a container with all install services, including a generic one for - * every v2 shell tool without its own service. + * every v2 shell tool. The generic ones are bound last, so a modern service + * wins over a custom v2 shell tool with the same name. */ async function prepareInstallContainer(): Promise { logger.trace('preparing install container'); diff --git a/src/cli/prepare-tool/index.spec.ts b/src/cli/prepare-tool/index.spec.ts index d46e93cc5e..dfeb0b4144 100644 --- a/src/cli/prepare-tool/index.spec.ts +++ b/src/cli/prepare-tool/index.spec.ts @@ -46,6 +46,8 @@ describe('cli/prepare-tool/index', () => { .spyOn(RubyPrepareService.prototype, 'prepare') .mockResolvedValue(); const v2 = vi.spyOn(V2ToolPrepareService.prototype, 'prepare'); + // keep ruby unprepared, so later tests don't initialize it + vi.spyOn(PathService.prototype, 'setPrepared').mockResolvedValue(); try { expect(await prepareTools(['ruby'])).toBeUndefined();