From 6096e626f074e4ef069936f4bea9951815197ff9 Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Tue, 28 Jul 2026 11:08:51 -0700 Subject: [PATCH 1/2] Add package manager command smoke tests --- .../managers/builtin/commands.unit.test.ts | 111 ++++++++++++++++++ src/test/managers/conda/commands.unit.test.ts | 73 ++++++++++++ .../managers/poetry/commands.unit.test.ts | 62 ++++++++++ 3 files changed, 246 insertions(+) create mode 100644 src/test/managers/builtin/commands.unit.test.ts create mode 100644 src/test/managers/conda/commands.unit.test.ts create mode 100644 src/test/managers/poetry/commands.unit.test.ts diff --git a/src/test/managers/builtin/commands.unit.test.ts b/src/test/managers/builtin/commands.unit.test.ts new file mode 100644 index 000000000..808203731 --- /dev/null +++ b/src/test/managers/builtin/commands.unit.test.ts @@ -0,0 +1,111 @@ +import assert from 'assert'; +import * as sinon from 'sinon'; +import { LogOutputChannel } from 'vscode'; +import * as workspaceApis from '../../../common/workspace.apis'; +import { PipAvailableVersionsCommand, UvAvailableVersionsCommand } from '../../../managers/builtin/commands/availableVersions'; +import { PipInstallCommand, UvInstallCommand } from '../../../managers/builtin/commands/install'; +import { PipListCommand, UvListCommand } from '../../../managers/builtin/commands/list'; +import { PipListDirectNamesCommand, UvListDirectNamesCommand } from '../../../managers/builtin/commands/listDirectNames'; +import { PipUninstallCommand, UvUninstallCommand } from '../../../managers/builtin/commands/uninstall'; +import { PipVersionCommand, UvVersionCommand } from '../../../managers/builtin/commands/version'; +import * as helpers from '../../../managers/builtin/helpers'; +import { createMockLogOutputChannel } from '../../mocks/helper'; + +suite('Pip and UV commands', () => { + let mockLog: LogOutputChannel; + let runPythonStub: sinon.SinonStub; + let runUvStub: sinon.SinonStub; + + setup(() => { + mockLog = createMockLogOutputChannel(); + sinon.stub(workspaceApis, 'getConfiguration').returns({ + get: () => undefined, + } as unknown as ReturnType); + runPythonStub = sinon.stub(helpers, 'runPython').resolves(''); + runUvStub = sinon.stub(helpers, 'runUV').resolves(''); + }); + + teardown(() => { + sinon.restore(); + }); + + test('PipAvailableVersionsCommand executes without error', async () => { + runPythonStub.resolves(JSON.stringify({ versions: ['1.0.0'] })); + const command = new PipAvailableVersionsCommand({ pythonExecutable: 'python', log: mockLog }); + + await assert.doesNotReject(() => command.execute({ packageName: 'package', pythonVersion: '3.13.1' })); + }); + + test('UvAvailableVersionsCommand executes without error', async () => { + runUvStub.resolves(JSON.stringify({ versions: ['1.0.0'] })); + const command = new UvAvailableVersionsCommand({ pythonExecutable: 'python', log: mockLog }); + + await assert.doesNotReject(() => command.execute({ packageName: 'package', pythonVersion: '3.13.1' })); + }); + + test('PipInstallCommand executes without error', async () => { + const command = new PipInstallCommand({ pythonExecutable: 'python', log: mockLog }); + + await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); + }); + + test('UvInstallCommand executes without error', async () => { + const command = new UvInstallCommand({ pythonExecutable: 'python', log: mockLog }); + + await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); + }); + + test('PipUninstallCommand executes without error', async () => { + const command = new PipUninstallCommand({ pythonExecutable: 'python', log: mockLog }); + + await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); + }); + + test('UvUninstallCommand executes without error', async () => { + const command = new UvUninstallCommand({ pythonExecutable: 'python', log: mockLog }); + + await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); + }); + + test('PipListCommand executes without error', async () => { + runPythonStub.resolves(JSON.stringify([{ name: 'package', version: '1.0.0' }])); + const command = new PipListCommand({ pythonExecutable: 'python', log: mockLog }); + + await assert.doesNotReject(() => command.execute()); + }); + + test('UvListCommand executes without error', async () => { + runUvStub.resolves(JSON.stringify([{ name: 'package', version: '1.0.0' }])); + const command = new UvListCommand({ pythonExecutable: 'python', log: mockLog }); + + await assert.doesNotReject(() => command.execute()); + }); + + test('PipListDirectNamesCommand executes without error', async () => { + runPythonStub.resolves(JSON.stringify([{ name: 'package', version: '1.0.0' }])); + const command = new PipListDirectNamesCommand({ pythonExecutable: 'python', log: mockLog }); + + await assert.doesNotReject(() => command.execute()); + }); + + test('UvListDirectNamesCommand executes without error', async () => { + runUvStub.resolves('package 1.0.0'); + const command = new UvListDirectNamesCommand({ pythonExecutable: 'python', log: mockLog }); + + await assert.doesNotReject(() => command.execute()); + }); + + test('PipVersionCommand executes without error', async () => { + runPythonStub.resolves('pip 24.0 from site-packages'); + const command = new PipVersionCommand({ pythonExecutable: 'python', log: mockLog }); + + await assert.doesNotReject(() => command.execute()); + }); + + test('UvVersionCommand executes without error', async () => { + runUvStub.resolves('uv 0.4.20'); + const command = new UvVersionCommand({ pythonExecutable: 'python', log: mockLog }); + + await assert.doesNotReject(() => command.execute()); + }); +}); \ No newline at end of file diff --git a/src/test/managers/conda/commands.unit.test.ts b/src/test/managers/conda/commands.unit.test.ts new file mode 100644 index 000000000..8b1d11f48 --- /dev/null +++ b/src/test/managers/conda/commands.unit.test.ts @@ -0,0 +1,73 @@ +import assert from 'assert'; +import * as sinon from 'sinon'; +import { LogOutputChannel } from 'vscode'; +import * as workspaceApis from '../../../common/workspace.apis'; +import { CondaAvailableVersionsCommand } from '../../../managers/conda/commands/availableVersions'; +import { CondaInstallCommand } from '../../../managers/conda/commands/install'; +import { CondaListCommand } from '../../../managers/conda/commands/list'; +import { CondaUninstallCommand } from '../../../managers/conda/commands/uninstall'; +import { CondaVersionCommand } from '../../../managers/conda/commands/version'; +import * as condaUtils from '../../../managers/conda/condaUtils'; +import { createMockLogOutputChannel } from '../../mocks/helper'; + +suite('Conda commands', () => { + let mockLog: LogOutputChannel; + let runCondaStub: sinon.SinonStub; + + setup(() => { + mockLog = createMockLogOutputChannel(); + sinon.stub(workspaceApis, 'getConfiguration').returns({ + get: () => undefined, + } as unknown as ReturnType); + runCondaStub = sinon.stub(condaUtils, 'runCondaExecutable').resolves(''); + }); + + teardown(() => { + sinon.restore(); + }); + + test('CondaAvailableVersionsCommand executes without error', async () => { + runCondaStub.resolves(JSON.stringify({ package: [{ version: '1.0.0' }] })); + const command = new CondaAvailableVersionsCommand({ pythonExecutable: 'conda', log: mockLog }); + + await assert.doesNotReject(() => command.execute({ packageName: 'package', pythonVersion: '' })); + }); + + test('CondaInstallCommand executes without error', async () => { + const command = new CondaInstallCommand({ + pythonExecutable: 'conda', + condaEnvironmentPath: 'environment', + log: mockLog, + }); + + await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); + }); + + test('CondaUninstallCommand executes without error', async () => { + const command = new CondaUninstallCommand({ + pythonExecutable: 'conda', + condaEnvironmentPath: 'environment', + log: mockLog, + }); + + await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); + }); + + test('CondaListCommand executes without error', async () => { + runCondaStub.resolves(JSON.stringify([{ name: 'package', version: '1.0.0' }])); + const command = new CondaListCommand({ + pythonExecutable: 'conda', + condaEnvironmentPath: 'environment', + log: mockLog, + }); + + await assert.doesNotReject(() => command.execute()); + }); + + test('CondaVersionCommand executes without error', async () => { + runCondaStub.resolves('conda 24.1.2'); + const command = new CondaVersionCommand({ pythonExecutable: 'conda', log: mockLog }); + + await assert.doesNotReject(() => command.execute()); + }); +}); \ No newline at end of file diff --git a/src/test/managers/poetry/commands.unit.test.ts b/src/test/managers/poetry/commands.unit.test.ts new file mode 100644 index 000000000..4deb7b058 --- /dev/null +++ b/src/test/managers/poetry/commands.unit.test.ts @@ -0,0 +1,62 @@ +import assert from 'assert'; +import * as sinon from 'sinon'; +import { LogOutputChannel } from 'vscode'; +import * as workspaceApis from '../../../common/workspace.apis'; +import { PoetryAddCommand } from '../../../managers/poetry/commands/add'; +import { PoetryRemoveCommand } from '../../../managers/poetry/commands/remove'; +import * as poetryRunner from '../../../managers/poetry/commands/runPoetry'; +import { PoetryShowCommand } from '../../../managers/poetry/commands/show'; +import { PoetryShowTopLevelCommand } from '../../../managers/poetry/commands/showTopLevel'; +import { PoetryVersionCommand } from '../../../managers/poetry/commands/version'; +import * as poetryUtils from '../../../managers/poetry/poetryUtils'; +import { createMockLogOutputChannel } from '../../mocks/helper'; + +suite('Poetry commands', () => { + let mockLog: LogOutputChannel; + let runPoetryStub: sinon.SinonStub; + + setup(() => { + mockLog = createMockLogOutputChannel(); + sinon.stub(workspaceApis, 'getConfiguration').returns({ + get: () => undefined, + } as unknown as ReturnType); + runPoetryStub = sinon.stub(poetryRunner, 'runPoetry').resolves(''); + sinon.stub(poetryUtils, 'getPoetryVersion').resolves('1.8.2'); + }); + + teardown(() => { + sinon.restore(); + }); + + test('PoetryAddCommand executes without error', async () => { + const command = new PoetryAddCommand({ pythonExecutable: 'poetry', cwd: 'project', log: mockLog }); + + await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); + }); + + test('PoetryRemoveCommand executes without error', async () => { + const command = new PoetryRemoveCommand({ pythonExecutable: 'poetry', cwd: 'project', log: mockLog }); + + await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); + }); + + test('PoetryShowCommand executes without error', async () => { + runPoetryStub.resolves('package 1.0.0 description'); + const command = new PoetryShowCommand({ pythonExecutable: 'poetry', cwd: 'project', log: mockLog }); + + await assert.doesNotReject(() => command.execute()); + }); + + test('PoetryShowTopLevelCommand executes without error', async () => { + runPoetryStub.resolves('package'); + const command = new PoetryShowTopLevelCommand({ pythonExecutable: 'poetry', cwd: 'project', log: mockLog }); + + await assert.doesNotReject(() => command.execute()); + }); + + test('PoetryVersionCommand executes without error', async () => { + const command = new PoetryVersionCommand({ pythonExecutable: 'poetry', log: mockLog }); + + await assert.doesNotReject(() => command.execute()); + }); +}); \ No newline at end of file From 42b5e9820841b250f4e68189b4d1835a4d4d3eb6 Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Thu, 30 Jul 2026 14:41:13 -0700 Subject: [PATCH 2/2] test: assert package manager command output parsing; fix uv uninstall doc Address PR review: strengthen command unit tests to verify output parsing behavior (package lists, direct names with normalization, version strings, available-version filtering) using representative runner output, instead of asserting the full argument vector (which merely restates buildCommand). Mutation commands remain smoke tests; their command strings are covered by integration tests. Also correct UvUninstallCommand doc comment: 'uv pip uninstall' has no -y/--yes flag and is non-interactive by default. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3f16397e-0917-4efb-8d75-566c71ebf9ba --- src/managers/builtin/commands/uninstall.ts | 3 +- .../managers/builtin/commands.unit.test.ts | 118 +++++++++++------- src/test/managers/conda/commands.unit.test.ts | 63 ++++++---- .../managers/poetry/commands.unit.test.ts | 47 ++++--- 4 files changed, 140 insertions(+), 91 deletions(-) diff --git a/src/managers/builtin/commands/uninstall.ts b/src/managers/builtin/commands/uninstall.ts index 50ffe61e4..f1c3c5908 100644 --- a/src/managers/builtin/commands/uninstall.ts +++ b/src/managers/builtin/commands/uninstall.ts @@ -25,7 +25,8 @@ export class PipUninstallCommand extends UninstallCommand { /** * UV uninstall command. - * Parsed command: `uv pip uninstall -y --python ` + * Parsed command: `uv pip uninstall --python ` + * Note: `uv pip uninstall` is non-interactive by default and has no `-y`/`--yes` option. * Official documentation: https://docs.astral.sh/uv/pip/ */ export class UvUninstallCommand extends UninstallCommand { diff --git a/src/test/managers/builtin/commands.unit.test.ts b/src/test/managers/builtin/commands.unit.test.ts index 808203731..69c9ad9b6 100644 --- a/src/test/managers/builtin/commands.unit.test.ts +++ b/src/test/managers/builtin/commands.unit.test.ts @@ -29,83 +29,109 @@ suite('Pip and UV commands', () => { sinon.restore(); }); - test('PipAvailableVersionsCommand executes without error', async () => { - runPythonStub.resolves(JSON.stringify({ versions: ['1.0.0'] })); - const command = new PipAvailableVersionsCommand({ pythonExecutable: 'python', log: mockLog }); - - await assert.doesNotReject(() => command.execute({ packageName: 'package', pythonVersion: '3.13.1' })); + // Mutation commands have no output to parse; unit coverage is a smoke test that + // execute() resolves. Their concrete command strings are exercised by integration tests. + suite('mutation commands execute without error', () => { + test('PipInstallCommand', async () => { + const command = new PipInstallCommand({ pythonExecutable: 'python', log: mockLog }); + await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); + }); + + test('UvInstallCommand', async () => { + const command = new UvInstallCommand({ pythonExecutable: 'python', log: mockLog }); + await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); + }); + + test('PipUninstallCommand', async () => { + const command = new PipUninstallCommand({ pythonExecutable: 'python', log: mockLog }); + await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); + }); + + test('UvUninstallCommand', async () => { + const command = new UvUninstallCommand({ pythonExecutable: 'python', log: mockLog }); + await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); + }); }); - test('UvAvailableVersionsCommand executes without error', async () => { - runUvStub.resolves(JSON.stringify({ versions: ['1.0.0'] })); - const command = new UvAvailableVersionsCommand({ pythonExecutable: 'python', log: mockLog }); - - await assert.doesNotReject(() => command.execute({ packageName: 'package', pythonVersion: '3.13.1' })); - }); - - test('PipInstallCommand executes without error', async () => { - const command = new PipInstallCommand({ pythonExecutable: 'python', log: mockLog }); - - await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); - }); + test('PipAvailableVersionsCommand parses versions and filters prereleases', async () => { + runPythonStub.resolves(JSON.stringify({ versions: ['2.0.0rc1', '1.0.0'] })); + const command = new PipAvailableVersionsCommand({ pythonExecutable: 'python', log: mockLog }); - test('UvInstallCommand executes without error', async () => { - const command = new UvInstallCommand({ pythonExecutable: 'python', log: mockLog }); + const result = await command.execute({ + packageName: 'package', + pythonVersion: '3.13.1', + includePrerelease: false, + }); - await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); + assert.deepStrictEqual(result.map((v) => v.public), ['1.0.0']); }); - test('PipUninstallCommand executes without error', async () => { - const command = new PipUninstallCommand({ pythonExecutable: 'python', log: mockLog }); - - await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); - }); + test('UvAvailableVersionsCommand parses versions from embedded JSON', async () => { + runUvStub.resolves(`Some preamble\n${JSON.stringify({ versions: ['1.0.0'] })}`); + const command = new UvAvailableVersionsCommand({ pythonExecutable: 'python', log: mockLog }); - test('UvUninstallCommand executes without error', async () => { - const command = new UvUninstallCommand({ pythonExecutable: 'python', log: mockLog }); + const result = await command.execute({ packageName: 'package', pythonVersion: '3.13.1' }); - await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); + assert.deepStrictEqual(result.map((v) => v.public), ['1.0.0']); }); - test('PipListCommand executes without error', async () => { - runPythonStub.resolves(JSON.stringify([{ name: 'package', version: '1.0.0' }])); + test('PipListCommand parses packages and drops entries missing a version', async () => { + runPythonStub.resolves(JSON.stringify([{ name: 'package', version: '1.0.0' }, { name: 'broken' }])); const command = new PipListCommand({ pythonExecutable: 'python', log: mockLog }); - await assert.doesNotReject(() => command.execute()); + const result = await command.execute(); + + assert.deepStrictEqual( + result.map((p) => ({ name: p.name, version: p.version })), + [{ name: 'package', version: '1.0.0' }], + ); }); - test('UvListCommand executes without error', async () => { + test('UvListCommand parses packages from JSON', async () => { runUvStub.resolves(JSON.stringify([{ name: 'package', version: '1.0.0' }])); const command = new UvListCommand({ pythonExecutable: 'python', log: mockLog }); - await assert.doesNotReject(() => command.execute()); + const result = await command.execute(); + + assert.deepStrictEqual( + result.map((p) => ({ name: p.name, version: p.version })), + [{ name: 'package', version: '1.0.0' }], + ); }); - test('PipListDirectNamesCommand executes without error', async () => { - runPythonStub.resolves(JSON.stringify([{ name: 'package', version: '1.0.0' }])); + test('PipListDirectNamesCommand returns normalized names', async () => { + runPythonStub.resolves(JSON.stringify([{ name: 'Flask_Thing', version: '1.0.0' }])); const command = new PipListDirectNamesCommand({ pythonExecutable: 'python', log: mockLog }); - await assert.doesNotReject(() => command.execute()); + const result = await command.execute(); + + assert.deepStrictEqual([...result], ['flask-thing']); }); - test('UvListDirectNamesCommand executes without error', async () => { - runUvStub.resolves('package 1.0.0'); + test('UvListDirectNamesCommand keeps top-level packages and skips indented dependencies', async () => { + runUvStub.resolves(['Flask_Thing 1.0.0', '├── dependency 2.0.0', '└── another-dep 3.0.0'].join('\n')); const command = new UvListDirectNamesCommand({ pythonExecutable: 'python', log: mockLog }); - await assert.doesNotReject(() => command.execute()); + const result = await command.execute(); + + assert.deepStrictEqual([...result], ['flask-thing']); }); - test('PipVersionCommand executes without error', async () => { - runPythonStub.resolves('pip 24.0 from site-packages'); + test('PipVersionCommand parses the version from pip --version output', async () => { + runPythonStub.resolves('pip 24.0 from /site-packages/pip (python 3.13)'); const command = new PipVersionCommand({ pythonExecutable: 'python', log: mockLog }); - await assert.doesNotReject(() => command.execute()); + const result = await command.execute(); + + assert.strictEqual(result?.public, '24.0'); }); - test('UvVersionCommand executes without error', async () => { - runUvStub.resolves('uv 0.4.20'); + test('UvVersionCommand parses the version from uv --version output', async () => { + runUvStub.resolves('uv 0.4.20 (abcdef 2024-01-01)'); const command = new UvVersionCommand({ pythonExecutable: 'python', log: mockLog }); - await assert.doesNotReject(() => command.execute()); + const result = await command.execute(); + + assert.strictEqual(result?.public, '0.4.20'); }); -}); \ No newline at end of file +}); diff --git a/src/test/managers/conda/commands.unit.test.ts b/src/test/managers/conda/commands.unit.test.ts index 8b1d11f48..5a2c09cd4 100644 --- a/src/test/managers/conda/commands.unit.test.ts +++ b/src/test/managers/conda/commands.unit.test.ts @@ -26,48 +26,59 @@ suite('Conda commands', () => { sinon.restore(); }); - test('CondaAvailableVersionsCommand executes without error', async () => { - runCondaStub.resolves(JSON.stringify({ package: [{ version: '1.0.0' }] })); - const command = new CondaAvailableVersionsCommand({ pythonExecutable: 'conda', log: mockLog }); - - await assert.doesNotReject(() => command.execute({ packageName: 'package', pythonVersion: '' })); - }); - - test('CondaInstallCommand executes without error', async () => { - const command = new CondaInstallCommand({ - pythonExecutable: 'conda', - condaEnvironmentPath: 'environment', - log: mockLog, + // Mutation commands have no output to parse; unit coverage is a smoke test that + // execute() resolves. Their concrete command strings are exercised by integration tests. + suite('mutation commands execute without error', () => { + test('CondaInstallCommand', async () => { + const command = new CondaInstallCommand({ + pythonExecutable: 'conda', + condaEnvironmentPath: 'environment', + log: mockLog, + }); + await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); }); - await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); + test('CondaUninstallCommand', async () => { + const command = new CondaUninstallCommand({ + pythonExecutable: 'conda', + condaEnvironmentPath: 'environment', + log: mockLog, + }); + await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); + }); }); - test('CondaUninstallCommand executes without error', async () => { - const command = new CondaUninstallCommand({ - pythonExecutable: 'conda', - condaEnvironmentPath: 'environment', - log: mockLog, - }); + test('CondaAvailableVersionsCommand parses versions keyed by package name', async () => { + runCondaStub.resolves(JSON.stringify({ package: [{ version: '1.0.0' }, { version: '2.0.0' }] })); + const command = new CondaAvailableVersionsCommand({ pythonExecutable: 'conda', log: mockLog }); + + const result = await command.execute({ packageName: 'package', pythonVersion: '' }); - await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); + assert.deepStrictEqual(result.map((v) => v.public), ['1.0.0', '2.0.0']); }); - test('CondaListCommand executes without error', async () => { - runCondaStub.resolves(JSON.stringify([{ name: 'package', version: '1.0.0' }])); + test('CondaListCommand parses packages and drops entries missing a version', async () => { + runCondaStub.resolves(JSON.stringify([{ name: 'package', version: '1.0.0' }, { name: 'broken' }])); const command = new CondaListCommand({ pythonExecutable: 'conda', condaEnvironmentPath: 'environment', log: mockLog, }); - await assert.doesNotReject(() => command.execute()); + const result = await command.execute(); + + assert.deepStrictEqual( + result.map((p) => ({ name: p.name, version: p.version })), + [{ name: 'package', version: '1.0.0' }], + ); }); - test('CondaVersionCommand executes without error', async () => { + test('CondaVersionCommand parses the version from conda --version output', async () => { runCondaStub.resolves('conda 24.1.2'); const command = new CondaVersionCommand({ pythonExecutable: 'conda', log: mockLog }); - await assert.doesNotReject(() => command.execute()); + const result = await command.execute(); + + assert.strictEqual(result?.public, '24.1.2'); }); -}); \ No newline at end of file +}); diff --git a/src/test/managers/poetry/commands.unit.test.ts b/src/test/managers/poetry/commands.unit.test.ts index 4deb7b058..37bb132e4 100644 --- a/src/test/managers/poetry/commands.unit.test.ts +++ b/src/test/managers/poetry/commands.unit.test.ts @@ -28,35 +28,46 @@ suite('Poetry commands', () => { sinon.restore(); }); - test('PoetryAddCommand executes without error', async () => { - const command = new PoetryAddCommand({ pythonExecutable: 'poetry', cwd: 'project', log: mockLog }); + // Mutation commands have no output to parse; unit coverage is a smoke test that + // execute() resolves. Their concrete command strings are exercised by integration tests. + suite('mutation commands execute without error', () => { + test('PoetryAddCommand', async () => { + const command = new PoetryAddCommand({ pythonExecutable: 'poetry', cwd: 'project', log: mockLog }); + await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); + }); - await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); + test('PoetryRemoveCommand', async () => { + const command = new PoetryRemoveCommand({ pythonExecutable: 'poetry', cwd: 'project', log: mockLog }); + await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); + }); }); - test('PoetryRemoveCommand executes without error', async () => { - const command = new PoetryRemoveCommand({ pythonExecutable: 'poetry', cwd: 'project', log: mockLog }); - - await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); - }); - - test('PoetryShowCommand executes without error', async () => { - runPoetryStub.resolves('package 1.0.0 description'); + test('PoetryShowCommand parses name, version and description', async () => { + runPoetryStub.resolves(['requests 2.31.0 Python HTTP for Humans.', ''].join('\n')); const command = new PoetryShowCommand({ pythonExecutable: 'poetry', cwd: 'project', log: mockLog }); - await assert.doesNotReject(() => command.execute()); + const result = await command.execute(); + + assert.deepStrictEqual( + result.map((p) => ({ name: p.name, version: p.version, description: p.description })), + [{ name: 'requests', version: '2.31.0', description: '2.31.0 - Python HTTP for Humans.' }], + ); }); - test('PoetryShowTopLevelCommand executes without error', async () => { - runPoetryStub.resolves('package'); + test('PoetryShowTopLevelCommand returns normalized names', async () => { + runPoetryStub.resolves(['Flask_Thing', 'requests'].join('\n')); const command = new PoetryShowTopLevelCommand({ pythonExecutable: 'poetry', cwd: 'project', log: mockLog }); - await assert.doesNotReject(() => command.execute()); + const result = await command.execute(); + + assert.deepStrictEqual([...result], ['flask-thing', 'requests']); }); - test('PoetryVersionCommand executes without error', async () => { + test('PoetryVersionCommand parses the poetry version', async () => { const command = new PoetryVersionCommand({ pythonExecutable: 'poetry', log: mockLog }); - await assert.doesNotReject(() => command.execute()); + const result = await command.execute(); + + assert.strictEqual(result?.public, '1.8.2'); }); -}); \ No newline at end of file +});