Skip to content

Commit f56998b

Browse files
committed
add tests
1 parent b18e19e commit f56998b

2 files changed

Lines changed: 235 additions & 44 deletions

File tree

src/managers/builtin/pipUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { Installable } from '../common/types';
1313
import { mergePackages } from '../common/utils';
1414
import { refreshPipPackages } from './utils';
1515

16-
function validatePyprojectToml(toml: tomljs.JsonMap): string | undefined {
16+
export function validatePyprojectToml(toml: tomljs.JsonMap): string | undefined {
1717
// 1. Validate package name (PEP 508)
1818
if (toml.project && (toml.project as tomljs.JsonMap).name) {
1919
const name = (toml.project as tomljs.JsonMap).name as string;
Lines changed: 234 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,258 @@
1+
import * as tomljs from '@iarna/toml';
12
import assert from 'assert';
23
import { Uri } from 'vscode';
3-
import { shouldProceedAfterPyprojectValidation, ValidationError } from '../../../managers/builtin/pipUtils';
4+
import {
5+
shouldProceedAfterPyprojectValidation,
6+
validatePyprojectToml,
7+
ValidationError,
8+
} from '../../../managers/builtin/pipUtils';
49

510
suite('pipUtils - validatePyproject', () => {
6-
const mockValidationError: ValidationError = {
7-
message: 'Invalid package name "my package" in pyproject.toml.',
8-
fileUri: Uri.file('/test/path/pyproject.toml'),
9-
};
11+
suite('shouldProceedAfterPyprojectValidation', () => {
12+
const mockValidationError: ValidationError = {
13+
message: 'Invalid package name "my package" in pyproject.toml.',
14+
fileUri: Uri.file('/test/path/pyproject.toml'),
15+
};
1016

11-
test('should return true when no validation error exists', async () => {
12-
// Arrange: no validation error
13-
const validationError = undefined;
14-
const install = ['-e', '/test/path'];
17+
test('should return true when no validation error exists', async () => {
18+
// Arrange: no validation error
19+
const validationError = undefined;
20+
const install = ['-e', '/test/path'];
1521

16-
// Act
17-
const result = await shouldProceedAfterPyprojectValidation(validationError, install);
22+
// Act
23+
const result = await shouldProceedAfterPyprojectValidation(validationError, install);
1824

19-
// Assert
20-
assert.strictEqual(result, true, 'Should proceed when no validation error');
21-
});
25+
// Assert
26+
assert.strictEqual(result, true, 'Should proceed when no validation error');
27+
});
28+
29+
test('should return true when install array is empty', async () => {
30+
// Arrange: validation error exists but no packages selected
31+
const install: string[] = [];
32+
33+
// Act
34+
const result = await shouldProceedAfterPyprojectValidation(mockValidationError, install);
35+
36+
// Assert
37+
assert.strictEqual(result, true, 'Should proceed when no packages selected');
38+
});
39+
40+
test('should return true when only requirements.txt packages selected (no -e flag)', async () => {
41+
// Arrange: validation error exists but only requirements.txt packages selected
42+
const install = ['-r', '/test/requirements.txt'];
43+
44+
// Act
45+
const result = await shouldProceedAfterPyprojectValidation(mockValidationError, install);
46+
47+
// Assert
48+
assert.strictEqual(result, true, 'Should proceed when no TOML packages selected');
49+
});
2250

23-
test('should return true when install array is empty', async () => {
24-
// Arrange: validation error exists but no packages selected
25-
const install: string[] = [];
51+
test('should return true when only PyPI packages selected (no flags at all)', async () => {
52+
// Arrange: only PyPI package names, no flags
53+
const install = ['numpy', 'pandas', 'requests'];
2654

27-
// Act
28-
const result = await shouldProceedAfterPyprojectValidation(mockValidationError, install);
55+
// Act
56+
const result = await shouldProceedAfterPyprojectValidation(mockValidationError, install);
2957

30-
// Assert
31-
assert.strictEqual(result, true, 'Should proceed when no packages selected');
58+
// Assert
59+
assert.strictEqual(result, true, 'Should proceed when only PyPI packages selected');
60+
});
61+
62+
test('should not trigger on -e flag at end of array without following argument', async () => {
63+
// Arrange: -e flag is last item (malformed, but should not crash)
64+
const install = ['numpy', '-e'];
65+
// This is edge case - -e at end means no path follows, so index + 1 < arr.length is false
66+
67+
// Act
68+
const result = await shouldProceedAfterPyprojectValidation(mockValidationError, install);
69+
70+
// Assert
71+
assert.strictEqual(result, true, 'Should not crash on malformed -e flag at end');
72+
});
3273
});
3374

34-
test('should return true when only requirements.txt packages selected (no -e flag)', async () => {
35-
// Arrange: validation error exists but only requirements.txt packages selected
36-
const install = ['-r', '/test/requirements.txt'];
75+
function verifyValidationError(toml: tomljs.JsonMap, expectedError: string | undefined) {
76+
const ActualError = validatePyprojectToml(toml);
77+
assert.strictEqual(ActualError, expectedError);
78+
}
79+
80+
suite('validatePyprojectToml - Package Name Validation (PEP 508)', () => {
81+
test('should accept valid single-character package name', () => {
82+
const toml: tomljs.JsonMap = {
83+
project: { name: 'a' } as tomljs.JsonMap,
84+
};
85+
verifyValidationError(toml, undefined);
86+
});
87+
88+
test('should accept valid package name with letters and numbers', () => {
89+
const toml: tomljs.JsonMap = {
90+
project: { name: 'mypackage123' } as tomljs.JsonMap,
91+
};
92+
verifyValidationError(toml, undefined);
93+
});
94+
95+
test('should accept valid package name with hyphens', () => {
96+
const toml: tomljs.JsonMap = {
97+
project: { name: 'my-package' } as tomljs.JsonMap,
98+
};
99+
verifyValidationError(toml, undefined);
100+
});
101+
102+
test('should accept valid package name with underscores', () => {
103+
const toml: tomljs.JsonMap = {
104+
project: { name: 'my_package' } as tomljs.JsonMap,
105+
};
106+
verifyValidationError(toml, undefined);
107+
});
108+
109+
test('should accept valid package name with dots', () => {
110+
const toml: tomljs.JsonMap = {
111+
project: { name: 'my.package' } as tomljs.JsonMap,
112+
};
113+
verifyValidationError(toml, undefined);
114+
});
115+
116+
test('should accept valid package name with mixed separators', () => {
117+
const toml: tomljs.JsonMap = {
118+
project: { name: 'my-package_name.v2' } as tomljs.JsonMap,
119+
};
120+
verifyValidationError(toml, undefined);
121+
});
122+
123+
test('should accept complex valid package name', () => {
124+
const toml: tomljs.JsonMap = {
125+
project: { name: 'Django-REST-framework' } as tomljs.JsonMap,
126+
};
127+
verifyValidationError(toml, undefined);
128+
});
129+
130+
test('should reject package name with spaces', () => {
131+
const toml: tomljs.JsonMap = {
132+
project: { name: 'my package' } as tomljs.JsonMap,
133+
};
134+
verifyValidationError(toml, 'Invalid package name "my package" in pyproject.toml.');
135+
});
136+
137+
test('should reject package name starting with hyphen', () => {
138+
const toml: tomljs.JsonMap = {
139+
project: { name: '-mypackage' } as tomljs.JsonMap,
140+
};
141+
verifyValidationError(toml, 'Invalid package name "-mypackage" in pyproject.toml.');
142+
});
143+
144+
test('should reject package name ending with hyphen', () => {
145+
const toml: tomljs.JsonMap = {
146+
project: { name: 'mypackage-' } as tomljs.JsonMap,
147+
};
148+
verifyValidationError(toml, 'Invalid package name "mypackage-" in pyproject.toml.');
149+
});
150+
151+
test('should reject package name starting with dot', () => {
152+
const toml: tomljs.JsonMap = {
153+
project: { name: '.mypackage' } as tomljs.JsonMap,
154+
};
155+
verifyValidationError(toml, 'Invalid package name ".mypackage" in pyproject.toml.');
156+
});
157+
158+
test('should reject package name ending with dot', () => {
159+
const toml: tomljs.JsonMap = {
160+
project: { name: 'mypackage.' } as tomljs.JsonMap,
161+
};
162+
verifyValidationError(toml, 'Invalid package name "mypackage." in pyproject.toml.');
163+
});
164+
165+
test('should reject package name starting with underscore', () => {
166+
const toml: tomljs.JsonMap = {
167+
project: { name: '_mypackage' } as tomljs.JsonMap,
168+
};
169+
verifyValidationError(toml, 'Invalid package name "_mypackage" in pyproject.toml.');
170+
});
171+
172+
test('should reject package name ending with underscore', () => {
173+
const toml: tomljs.JsonMap = {
174+
project: { name: 'mypackage_' } as tomljs.JsonMap,
175+
};
176+
verifyValidationError(toml, 'Invalid package name "mypackage_" in pyproject.toml.');
177+
});
178+
179+
test('should reject empty package name', () => {
180+
const toml: tomljs.JsonMap = {
181+
project: { name: '' } as tomljs.JsonMap,
182+
};
183+
verifyValidationError(toml, 'Invalid package name "" in pyproject.toml.');
184+
});
185+
186+
test('should reject package name with special characters', () => {
187+
const toml: tomljs.JsonMap = {
188+
project: { name: 'my@package' } as tomljs.JsonMap,
189+
};
190+
verifyValidationError(toml, 'Invalid package name "my@package" in pyproject.toml.');
191+
});
37192

38-
// Act
39-
const result = await shouldProceedAfterPyprojectValidation(mockValidationError, install);
193+
test('should reject package name with only separator', () => {
194+
const toml: tomljs.JsonMap = {
195+
project: { name: '-' } as tomljs.JsonMap,
196+
};
197+
verifyValidationError(toml, 'Invalid package name "-" in pyproject.toml.');
198+
});
40199

41-
// Assert
42-
assert.strictEqual(result, true, 'Should proceed when no TOML packages selected');
200+
test('should accept when no project section exists', () => {
201+
const toml: tomljs.JsonMap = {};
202+
verifyValidationError(toml, undefined);
203+
});
43204
});
44205

45-
test('should return true when only PyPI packages selected (no flags at all)', async () => {
46-
// Arrange: only PyPI package names, no flags
47-
const install = ['numpy', 'pandas', 'requests'];
206+
suite('validatePyprojectToml - Required Fields (PEP 621)', () => {
207+
test('should accept valid project with name', () => {
208+
const toml: tomljs.JsonMap = {
209+
project: { name: 'test' } as tomljs.JsonMap,
210+
};
211+
verifyValidationError(toml, undefined);
212+
});
48213

49-
// Act
50-
const result = await shouldProceedAfterPyprojectValidation(mockValidationError, install);
214+
test('should reject project without name field', () => {
215+
const toml: tomljs.JsonMap = {
216+
project: { version: '1.0.0' } as tomljs.JsonMap,
217+
};
218+
verifyValidationError(toml, 'Missing required field "name" in [project] section of pyproject.toml.');
219+
});
51220

52-
// Assert
53-
assert.strictEqual(result, true, 'Should proceed when only PyPI packages selected');
221+
test('should accept when no project section exists', () => {
222+
const toml: tomljs.JsonMap = {};
223+
verifyValidationError(toml, undefined);
224+
});
54225
});
55226

56-
test('should not trigger on -e flag at end of array without following argument', async () => {
57-
// Arrange: -e flag is last item (malformed, but should not crash)
58-
const install = ['numpy', '-e'];
59-
// This is edge case - -e at end means no path follows, so index + 1 < arr.length is false
227+
suite('validatePyprojectToml - Build System (PEP 518)', () => {
228+
test('should accept valid build-system with requires', () => {
229+
const toml: tomljs.JsonMap = {
230+
project: { name: 'test' } as tomljs.JsonMap,
231+
'build-system': {
232+
requires: ['setuptools', 'wheel'],
233+
} as tomljs.JsonMap,
234+
};
235+
verifyValidationError(toml, undefined);
236+
});
60237

61-
// Act
62-
const result = await shouldProceedAfterPyprojectValidation(mockValidationError, install);
238+
test('should reject build-system without requires field', () => {
239+
const toml: tomljs.JsonMap = {
240+
project: { name: 'test' } as tomljs.JsonMap,
241+
'build-system': {
242+
'build-backend': 'setuptools.build_meta',
243+
} as tomljs.JsonMap,
244+
};
245+
verifyValidationError(
246+
toml,
247+
'Missing required field "requires" in [build-system] section of pyproject.toml.',
248+
);
249+
});
63250

64-
// Assert
65-
assert.strictEqual(result, true, 'Should not crash on malformed -e flag at end');
251+
test('should accept when no build-system section exists', () => {
252+
const toml: tomljs.JsonMap = {
253+
project: { name: 'test' } as tomljs.JsonMap,
254+
};
255+
verifyValidationError(toml, undefined);
256+
});
66257
});
67258
});

0 commit comments

Comments
 (0)