Skip to content

Commit 39f8559

Browse files
committed
test: restore package command parser coverage
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6b2fe9b5-38ea-442f-b07a-b6c71134d480
1 parent e4c7f10 commit 39f8559

1 file changed

Lines changed: 188 additions & 0 deletions

File tree

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import { explain } from '@renovatebot/pep440';
5+
import assert from 'assert';
6+
import * as fs from 'fs-extra';
7+
import * as path from 'path';
8+
import * as sinon from 'sinon';
9+
import { LogOutputChannel } from 'vscode';
10+
import * as workspaceApis from '../../../common/workspace.apis';
11+
import {
12+
PipAvailableVersionsCommand,
13+
UvAvailableVersionsCommand,
14+
} from '../../../managers/builtin/commands/availableVersions';
15+
import { PipListCommand, UvListCommand } from '../../../managers/builtin/commands/list';
16+
import { PipListDirectNamesCommand, UvListDirectNamesCommand } from '../../../managers/builtin/commands/listDirectNames';
17+
import * as helpers from '../../../managers/builtin/helpers';
18+
import { EXTENSION_TEST_ROOT } from '../../constants';
19+
import { createMockLogOutputChannel } from '../../mocks/helper';
20+
21+
suite('Pip and UV command parsing', () => {
22+
const testDataRoot = path.join(EXTENSION_TEST_ROOT, 'managers', 'builtin');
23+
let log: LogOutputChannel;
24+
let runPythonStub: sinon.SinonStub;
25+
let runUvStub: sinon.SinonStub;
26+
27+
setup(() => {
28+
log = createMockLogOutputChannel();
29+
sinon.stub(workspaceApis, 'getConfiguration').returns({
30+
get: () => undefined,
31+
} as unknown as ReturnType<typeof workspaceApis.getConfiguration>);
32+
runPythonStub = sinon.stub(helpers, 'runPython').resolves('');
33+
runUvStub = sinon.stub(helpers, 'runUV').resolves('');
34+
});
35+
36+
teardown(() => {
37+
sinon.restore();
38+
});
39+
40+
for (const testName of ['piplist1', 'piplist2', 'piplist3']) {
41+
test(`PipListCommand parses ${testName}`, async () => {
42+
const expected = JSON.parse(
43+
await fs.readFile(path.join(testDataRoot, `${testName}.expected.json`), 'utf8'),
44+
) as { packages: { name: string; version: string }[] };
45+
runPythonStub.resolves(JSON.stringify(expected.packages));
46+
const command = new PipListCommand({ pythonExecutable: 'python', log });
47+
48+
const packages = await command.execute();
49+
50+
assert.deepStrictEqual(
51+
packages,
52+
expected.packages.map(({ name, version }) => ({
53+
name,
54+
version,
55+
displayName: name,
56+
description: version,
57+
})),
58+
);
59+
});
60+
}
61+
62+
test('PipListCommand returns no packages and logs malformed JSON', async () => {
63+
runPythonStub.resolves('not json');
64+
const command = new PipListCommand({ pythonExecutable: 'python', log });
65+
66+
assert.deepStrictEqual(await command.execute(), []);
67+
assert.strictEqual((log.error as sinon.SinonStub).callCount, 1);
68+
});
69+
70+
test('PipListCommand rejects non-array output and incomplete entries', async () => {
71+
const command = new PipListCommand({ pythonExecutable: 'python', log });
72+
runPythonStub.resolves('{"name":"pip","version":"24.0"}');
73+
assert.deepStrictEqual(await command.execute(), []);
74+
75+
runPythonStub.resolves(
76+
JSON.stringify([{ name: 'pip', version: '24.0' }, { name: 'setuptools' }, { version: '1.0' }]),
77+
);
78+
assert.deepStrictEqual(await command.execute(), [
79+
{
80+
name: 'pip',
81+
version: '24.0',
82+
displayName: 'pip',
83+
description: '24.0',
84+
},
85+
]);
86+
});
87+
88+
test('UvListCommand handles valid and malformed JSON', async () => {
89+
const command = new UvListCommand({ pythonExecutable: 'python', log });
90+
runUvStub.resolves(JSON.stringify([{ name: 'pip', version: '24.0' }]));
91+
assert.deepStrictEqual(await command.execute(), [
92+
{
93+
name: 'pip',
94+
version: '24.0',
95+
displayName: 'pip',
96+
description: '24.0',
97+
},
98+
]);
99+
100+
runUvStub.resolves('not json');
101+
assert.deepStrictEqual(await command.execute(), []);
102+
});
103+
104+
test('PipListDirectNamesCommand normalizes names and ignores invalid entries', async () => {
105+
runPythonStub.resolves(JSON.stringify([{ name: 'My_Package' }, { version: '1.0' }, { name: '' }]));
106+
const command = new PipListDirectNamesCommand({ pythonExecutable: 'python', log });
107+
108+
assert.deepStrictEqual(await command.execute(), new Set(['my-package']));
109+
});
110+
111+
test('UvListDirectNamesCommand parses top-level packages only', async () => {
112+
runUvStub.resolves(
113+
[
114+
'My_Package v1.0.0',
115+
'├── dependency v2.0.0',
116+
'│ └── nested-dependency v3.0.0',
117+
'another.package v4.0.0',
118+
'',
119+
].join('\n'),
120+
);
121+
const command = new UvListDirectNamesCommand({ pythonExecutable: 'python', log });
122+
123+
assert.deepStrictEqual(await command.execute(), new Set(['my-package', 'another-package']));
124+
});
125+
126+
test('UvListDirectNamesCommand handles empty and indented-only output', async () => {
127+
const command = new UvListDirectNamesCommand({ pythonExecutable: 'python', log });
128+
runUvStub.resolves('');
129+
assert.deepStrictEqual(await command.execute(), new Set());
130+
131+
runUvStub.resolves(' indented v1.0.0\n└── dependency v2.0.0');
132+
assert.deepStrictEqual(await command.execute(), new Set());
133+
});
134+
135+
test('PipAvailableVersionsCommand parses, deduplicates, and filters versions', async () => {
136+
runPythonStub.resolves(
137+
`warning before JSON\n${JSON.stringify({
138+
versions: ['2.0.0', '2.0.0', '2.1.0rc1', ' invalid ', '1.0.0'],
139+
})}\nwarning after JSON`,
140+
);
141+
const command = new PipAvailableVersionsCommand({ pythonExecutable: 'python', log });
142+
143+
const versions = await command.execute({
144+
packageName: 'package',
145+
pythonVersion: '3.13',
146+
includePrerelease: false,
147+
});
148+
149+
assert.deepStrictEqual(
150+
versions.map((version) => version.public),
151+
['2.0.0', '1.0.0'],
152+
);
153+
});
154+
155+
test('PipAvailableVersionsCommand handles malformed output', async () => {
156+
const command = new PipAvailableVersionsCommand({ pythonExecutable: 'python', log });
157+
runPythonStub.resolves('no JSON here');
158+
assert.deepStrictEqual(
159+
await command.execute({ packageName: 'package', pythonVersion: '3.13' }),
160+
[],
161+
);
162+
163+
runPythonStub.resolves('{not valid JSON}');
164+
assert.deepStrictEqual(
165+
await command.execute({ packageName: 'package', pythonVersion: '3.13' }),
166+
[],
167+
);
168+
169+
runPythonStub.resolves(JSON.stringify({ versions: '1.0.0' }));
170+
assert.deepStrictEqual(
171+
await command.execute({ packageName: 'package', pythonVersion: '3.13' }),
172+
[],
173+
);
174+
});
175+
176+
test('UvAvailableVersionsCommand returns parsed prerelease versions when requested', async () => {
177+
runUvStub.resolves(JSON.stringify({ versions: ['1.0.0', '2.0.0rc1'] }));
178+
const command = new UvAvailableVersionsCommand({ pythonExecutable: 'python', log });
179+
180+
const versions = await command.execute({
181+
packageName: 'package',
182+
pythonVersion: '3.13',
183+
includePrerelease: true,
184+
});
185+
186+
assert.deepStrictEqual(versions, [explain('1.0.0'), explain('2.0.0rc1')]);
187+
});
188+
});

0 commit comments

Comments
 (0)