uv-lock-report 0.12.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- uv_lock_report/__init__.py +32 -0
- uv_lock_report/__main__.py +11 -0
- uv_lock_report/cli.py +41 -0
- uv_lock_report/models.py +430 -0
- uv_lock_report/report.py +68 -0
- uv_lock_report/tests/__init__.py +0 -0
- uv_lock_report/tests/conftest.py +125 -0
- uv_lock_report/tests/test_get_lockfiles.py +324 -0
- uv_lock_report/tests/test_lock_file_reporter.py +615 -0
- uv_lock_report/tests/test_lockfile.py +47 -0
- uv_lock_report/tests/test_lockfile_changes.py +71 -0
- uv_lock_report/tests/test_updated_package.py +11 -0
- uv_lock_report/tests/test_version_change_level.py +52 -0
- uv_lock_report-0.12.1.dist-info/METADATA +133 -0
- uv_lock_report-0.12.1.dist-info/RECORD +17 -0
- uv_lock_report-0.12.1.dist-info/WHEEL +4 -0
- uv_lock_report-0.12.1.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
from typing import Any, cast
|
|
2
|
+
|
|
3
|
+
from uv_lock_report.models import (
|
|
4
|
+
LockfilePackage,
|
|
5
|
+
LockFileReporter,
|
|
6
|
+
LockFileType,
|
|
7
|
+
OutputFormat,
|
|
8
|
+
UpdatedPackage,
|
|
9
|
+
UvLockFile,
|
|
10
|
+
VersionChangeLevel,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestLockFileReporter:
|
|
15
|
+
"""Test the LockFileReporter class for comparing lockfiles and detecting changes."""
|
|
16
|
+
|
|
17
|
+
def test_both_lockfiles_none(self):
|
|
18
|
+
"""Test when both old and new lockfiles are None."""
|
|
19
|
+
reporter = LockFileReporter(
|
|
20
|
+
old_lockfile=None,
|
|
21
|
+
new_lockfile=None,
|
|
22
|
+
output_format=OutputFormat.TABLE,
|
|
23
|
+
show_learn_more_link=True,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
assert reporter.added_package_names == set()
|
|
27
|
+
assert reporter.removed_package_names == set()
|
|
28
|
+
assert reporter.both_lockfile_package_names == set()
|
|
29
|
+
|
|
30
|
+
changes = reporter.get_changes()
|
|
31
|
+
assert changes.added == []
|
|
32
|
+
assert changes.removed == []
|
|
33
|
+
assert changes.updated == []
|
|
34
|
+
assert changes.items == 0
|
|
35
|
+
|
|
36
|
+
def test_old_lockfile_none_new_lockfile_has_packages(self):
|
|
37
|
+
"""Test when old lockfile is None and new lockfile has packages (initial lockfile)."""
|
|
38
|
+
new_packages = [
|
|
39
|
+
LockfilePackage(name="pkg1", version="1.0.0"),
|
|
40
|
+
LockfilePackage(name="pkg2", version="2.0.0"),
|
|
41
|
+
]
|
|
42
|
+
new_lockfile = UvLockFile(
|
|
43
|
+
type=LockFileType.UV,
|
|
44
|
+
version=1,
|
|
45
|
+
revision=1,
|
|
46
|
+
**cast(Any, {"package": new_packages, "requires-python": ">=3.9"}),
|
|
47
|
+
)
|
|
48
|
+
reporter = LockFileReporter(
|
|
49
|
+
old_lockfile=None,
|
|
50
|
+
new_lockfile=new_lockfile,
|
|
51
|
+
output_format=OutputFormat.TABLE,
|
|
52
|
+
show_learn_more_link=True,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
assert reporter.added_package_names == {"pkg1", "pkg2"}
|
|
56
|
+
assert reporter.removed_package_names == set()
|
|
57
|
+
assert reporter.both_lockfile_package_names == set()
|
|
58
|
+
|
|
59
|
+
changes = reporter.get_changes()
|
|
60
|
+
assert len(changes.added) == 2
|
|
61
|
+
assert changes.removed == []
|
|
62
|
+
assert changes.updated == []
|
|
63
|
+
assert changes.items == 2
|
|
64
|
+
|
|
65
|
+
def test_new_lockfile_none_old_lockfile_has_packages(self):
|
|
66
|
+
"""Test when new lockfile is None and old lockfile has packages (lockfile deleted)."""
|
|
67
|
+
old_packages = [
|
|
68
|
+
LockfilePackage(name="pkg1", version="1.0.0"),
|
|
69
|
+
LockfilePackage(name="pkg2", version="2.0.0"),
|
|
70
|
+
]
|
|
71
|
+
old_lockfile = UvLockFile(
|
|
72
|
+
type=LockFileType.UV,
|
|
73
|
+
version=1,
|
|
74
|
+
revision=1,
|
|
75
|
+
**cast(Any, {"package": old_packages, "requires-python": ">=3.9"}),
|
|
76
|
+
)
|
|
77
|
+
reporter = LockFileReporter(
|
|
78
|
+
old_lockfile=old_lockfile,
|
|
79
|
+
new_lockfile=None,
|
|
80
|
+
output_format=OutputFormat.TABLE,
|
|
81
|
+
show_learn_more_link=True,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
assert reporter.added_package_names == set()
|
|
85
|
+
assert reporter.removed_package_names == {"pkg1", "pkg2"}
|
|
86
|
+
assert reporter.both_lockfile_package_names == set()
|
|
87
|
+
|
|
88
|
+
changes = reporter.get_changes()
|
|
89
|
+
assert changes.added == []
|
|
90
|
+
assert len(changes.removed) == 2
|
|
91
|
+
assert changes.updated == []
|
|
92
|
+
assert changes.items == 2
|
|
93
|
+
|
|
94
|
+
def test_no_changes(self):
|
|
95
|
+
"""Test when both lockfiles are identical."""
|
|
96
|
+
packages = [
|
|
97
|
+
LockfilePackage(name="pkg1", version="1.0.0"),
|
|
98
|
+
LockfilePackage(name="pkg2", version="2.0.0"),
|
|
99
|
+
]
|
|
100
|
+
old_lockfile = UvLockFile(
|
|
101
|
+
type=LockFileType.UV,
|
|
102
|
+
version=1,
|
|
103
|
+
revision=1,
|
|
104
|
+
**cast(Any, {"package": packages, "requires-python": ">=3.9"}),
|
|
105
|
+
)
|
|
106
|
+
new_lockfile = UvLockFile(
|
|
107
|
+
type=LockFileType.UV,
|
|
108
|
+
version=1,
|
|
109
|
+
revision=1,
|
|
110
|
+
**cast(Any, {"package": packages, "requires-python": ">=3.9"}),
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
reporter = LockFileReporter(
|
|
114
|
+
old_lockfile=old_lockfile,
|
|
115
|
+
new_lockfile=new_lockfile,
|
|
116
|
+
output_format=OutputFormat.TABLE,
|
|
117
|
+
show_learn_more_link=False,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
assert reporter.added_package_names == set()
|
|
121
|
+
assert reporter.removed_package_names == set()
|
|
122
|
+
assert reporter.both_lockfile_package_names == {"pkg1", "pkg2"}
|
|
123
|
+
|
|
124
|
+
changes = reporter.get_changes()
|
|
125
|
+
assert changes.added == []
|
|
126
|
+
assert changes.removed == []
|
|
127
|
+
assert changes.updated == []
|
|
128
|
+
assert changes.items == 0
|
|
129
|
+
|
|
130
|
+
def test_added_packages_only(self):
|
|
131
|
+
"""Test when only new packages are added."""
|
|
132
|
+
old_packages = [
|
|
133
|
+
LockfilePackage(name="pkg1", version="1.0.0"),
|
|
134
|
+
]
|
|
135
|
+
new_packages = [
|
|
136
|
+
LockfilePackage(name="pkg1", version="1.0.0"),
|
|
137
|
+
LockfilePackage(name="pkg2", version="2.0.0"),
|
|
138
|
+
LockfilePackage(name="pkg3", version="3.0.0"),
|
|
139
|
+
]
|
|
140
|
+
old_lockfile = UvLockFile(
|
|
141
|
+
type=LockFileType.UV,
|
|
142
|
+
version=1,
|
|
143
|
+
revision=1,
|
|
144
|
+
**cast(Any, {"package": old_packages, "requires-python": ">=3.9"}),
|
|
145
|
+
)
|
|
146
|
+
new_lockfile = UvLockFile(
|
|
147
|
+
type=LockFileType.UV,
|
|
148
|
+
version=1,
|
|
149
|
+
revision=1,
|
|
150
|
+
**cast(Any, {"package": new_packages, "requires-python": ">=3.9"}),
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
reporter = LockFileReporter(
|
|
154
|
+
old_lockfile=old_lockfile,
|
|
155
|
+
new_lockfile=new_lockfile,
|
|
156
|
+
output_format=OutputFormat.TABLE,
|
|
157
|
+
show_learn_more_link=True,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
assert reporter.added_package_names == {"pkg2", "pkg3"}
|
|
161
|
+
assert reporter.removed_package_names == set()
|
|
162
|
+
assert reporter.both_lockfile_package_names == {"pkg1"}
|
|
163
|
+
|
|
164
|
+
changes = reporter.get_changes()
|
|
165
|
+
assert len(changes.added) == 2
|
|
166
|
+
assert {pkg.name for pkg in changes.added} == {"pkg2", "pkg3"}
|
|
167
|
+
assert changes.removed == []
|
|
168
|
+
assert changes.updated == []
|
|
169
|
+
assert changes.items == 2
|
|
170
|
+
|
|
171
|
+
def test_removed_packages_only(self):
|
|
172
|
+
"""Test when only packages are removed."""
|
|
173
|
+
old_packages = [
|
|
174
|
+
LockfilePackage(name="pkg1", version="1.0.0"),
|
|
175
|
+
LockfilePackage(name="pkg2", version="2.0.0"),
|
|
176
|
+
LockfilePackage(name="pkg3", version="3.0.0"),
|
|
177
|
+
]
|
|
178
|
+
new_packages = [
|
|
179
|
+
LockfilePackage(name="pkg1", version="1.0.0"),
|
|
180
|
+
]
|
|
181
|
+
old_lockfile = UvLockFile(
|
|
182
|
+
type=LockFileType.UV,
|
|
183
|
+
version=1,
|
|
184
|
+
revision=1,
|
|
185
|
+
**cast(Any, {"package": old_packages, "requires-python": ">=3.9"}),
|
|
186
|
+
)
|
|
187
|
+
new_lockfile = UvLockFile(
|
|
188
|
+
type=LockFileType.UV,
|
|
189
|
+
version=1,
|
|
190
|
+
revision=1,
|
|
191
|
+
**cast(Any, {"package": new_packages, "requires-python": ">=3.9"}),
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
reporter = LockFileReporter(
|
|
195
|
+
old_lockfile=old_lockfile,
|
|
196
|
+
new_lockfile=new_lockfile,
|
|
197
|
+
output_format=OutputFormat.TABLE,
|
|
198
|
+
show_learn_more_link=True,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
assert reporter.added_package_names == set()
|
|
202
|
+
assert reporter.removed_package_names == {"pkg2", "pkg3"}
|
|
203
|
+
assert reporter.both_lockfile_package_names == {"pkg1"}
|
|
204
|
+
|
|
205
|
+
changes = reporter.get_changes()
|
|
206
|
+
assert changes.added == []
|
|
207
|
+
assert len(changes.removed) == 2
|
|
208
|
+
assert {pkg.name for pkg in changes.removed} == {"pkg2", "pkg3"}
|
|
209
|
+
assert changes.updated == []
|
|
210
|
+
assert changes.items == 2
|
|
211
|
+
|
|
212
|
+
def test_updated_packages_only(self):
|
|
213
|
+
"""Test when only package versions are updated."""
|
|
214
|
+
old_packages = [
|
|
215
|
+
LockfilePackage(name="pkg1", version="1.0.0"),
|
|
216
|
+
LockfilePackage(name="pkg2", version="2.0.0"),
|
|
217
|
+
]
|
|
218
|
+
new_packages = [
|
|
219
|
+
LockfilePackage(name="pkg1", version="1.5.0"),
|
|
220
|
+
LockfilePackage(name="pkg2", version="3.0.0"),
|
|
221
|
+
]
|
|
222
|
+
old_lockfile = UvLockFile(
|
|
223
|
+
type=LockFileType.UV,
|
|
224
|
+
version=1,
|
|
225
|
+
revision=1,
|
|
226
|
+
**cast(Any, {"package": old_packages, "requires-python": ">=3.9"}),
|
|
227
|
+
)
|
|
228
|
+
new_lockfile = UvLockFile(
|
|
229
|
+
type=LockFileType.UV,
|
|
230
|
+
version=1,
|
|
231
|
+
revision=1,
|
|
232
|
+
**cast(Any, {"package": new_packages, "requires-python": ">=3.9"}),
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
reporter = LockFileReporter(
|
|
236
|
+
old_lockfile=old_lockfile,
|
|
237
|
+
new_lockfile=new_lockfile,
|
|
238
|
+
output_format=OutputFormat.TABLE,
|
|
239
|
+
show_learn_more_link=False,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
assert reporter.added_package_names == set()
|
|
243
|
+
assert reporter.removed_package_names == set()
|
|
244
|
+
assert reporter.both_lockfile_package_names == {"pkg1", "pkg2"}
|
|
245
|
+
|
|
246
|
+
changes = reporter.get_changes()
|
|
247
|
+
assert changes.added == []
|
|
248
|
+
assert changes.removed == []
|
|
249
|
+
assert len(changes.updated) == 2
|
|
250
|
+
assert changes.items == 2
|
|
251
|
+
|
|
252
|
+
# Verify the update details
|
|
253
|
+
updates_by_name = {pkg.name: pkg for pkg in changes.updated}
|
|
254
|
+
assert updates_by_name["pkg1"].old_version == "1.0.0"
|
|
255
|
+
assert updates_by_name["pkg1"].new_version == "1.5.0"
|
|
256
|
+
assert updates_by_name["pkg2"].old_version == "2.0.0"
|
|
257
|
+
assert updates_by_name["pkg2"].new_version == "3.0.0"
|
|
258
|
+
|
|
259
|
+
def test_mixed_changes(self):
|
|
260
|
+
"""Test when packages are added, removed, and updated."""
|
|
261
|
+
old_packages = [
|
|
262
|
+
LockfilePackage(name="pkg1", version="1.0.0"),
|
|
263
|
+
LockfilePackage(name="pkg2", version="2.0.0"),
|
|
264
|
+
LockfilePackage(name="pkg3", version="3.0.0"),
|
|
265
|
+
]
|
|
266
|
+
new_packages = [
|
|
267
|
+
LockfilePackage(name="pkg1", version="1.5.0"), # Updated
|
|
268
|
+
LockfilePackage(name="pkg3", version="3.0.0"), # Unchanged
|
|
269
|
+
LockfilePackage(name="pkg4", version="4.0.0"), # Added
|
|
270
|
+
]
|
|
271
|
+
# pkg2 is removed
|
|
272
|
+
|
|
273
|
+
old_lockfile = UvLockFile(
|
|
274
|
+
type=LockFileType.UV,
|
|
275
|
+
version=1,
|
|
276
|
+
revision=1,
|
|
277
|
+
**cast(Any, {"package": old_packages, "requires-python": ">=3.9"}),
|
|
278
|
+
)
|
|
279
|
+
new_lockfile = UvLockFile(
|
|
280
|
+
type=LockFileType.UV,
|
|
281
|
+
version=1,
|
|
282
|
+
revision=1,
|
|
283
|
+
**cast(Any, {"package": new_packages, "requires-python": ">=3.9"}),
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
reporter = LockFileReporter(
|
|
287
|
+
old_lockfile=old_lockfile,
|
|
288
|
+
new_lockfile=new_lockfile,
|
|
289
|
+
output_format=OutputFormat.TABLE,
|
|
290
|
+
show_learn_more_link=True,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
assert reporter.added_package_names == {"pkg4"}
|
|
294
|
+
assert reporter.removed_package_names == {"pkg2"}
|
|
295
|
+
assert reporter.both_lockfile_package_names == {"pkg1", "pkg3"}
|
|
296
|
+
|
|
297
|
+
changes = reporter.get_changes()
|
|
298
|
+
assert len(changes.added) == 1
|
|
299
|
+
assert changes.added[0].name == "pkg4"
|
|
300
|
+
|
|
301
|
+
assert len(changes.removed) == 1
|
|
302
|
+
assert changes.removed[0].name == "pkg2"
|
|
303
|
+
|
|
304
|
+
assert len(changes.updated) == 1
|
|
305
|
+
assert changes.updated[0].name == "pkg1"
|
|
306
|
+
assert changes.updated[0].old_version == "1.0.0"
|
|
307
|
+
assert changes.updated[0].new_version == "1.5.0"
|
|
308
|
+
|
|
309
|
+
assert changes.items == 3
|
|
310
|
+
|
|
311
|
+
def test_package_version_string_handling(self):
|
|
312
|
+
"""Test packages with string versions (malformed versions)."""
|
|
313
|
+
old_packages = [
|
|
314
|
+
LockfilePackage(name="pkg1", version="2.9.0.post0"),
|
|
315
|
+
]
|
|
316
|
+
new_packages = [
|
|
317
|
+
LockfilePackage(name="pkg1", version="2.10.0.post0"),
|
|
318
|
+
]
|
|
319
|
+
old_lockfile = UvLockFile(
|
|
320
|
+
type=LockFileType.UV,
|
|
321
|
+
version=1,
|
|
322
|
+
revision=1,
|
|
323
|
+
**cast(Any, {"package": old_packages, "requires-python": ">=3.9"}),
|
|
324
|
+
)
|
|
325
|
+
new_lockfile = UvLockFile(
|
|
326
|
+
type=LockFileType.UV,
|
|
327
|
+
version=1,
|
|
328
|
+
revision=1,
|
|
329
|
+
**cast(Any, {"package": new_packages, "requires-python": ">=3.9"}),
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
reporter = LockFileReporter(
|
|
333
|
+
old_lockfile=old_lockfile,
|
|
334
|
+
new_lockfile=new_lockfile,
|
|
335
|
+
output_format=OutputFormat.TABLE,
|
|
336
|
+
show_learn_more_link=True,
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
changes = reporter.get_changes()
|
|
340
|
+
assert len(changes.updated) == 1
|
|
341
|
+
assert changes.updated[0].name == "pkg1"
|
|
342
|
+
assert str(changes.updated[0].old_version) == "2.9.0.post0"
|
|
343
|
+
assert str(changes.updated[0].new_version) == "2.10.0.post0"
|
|
344
|
+
|
|
345
|
+
def test_get_added_packages_order_preserved(self):
|
|
346
|
+
"""Test that the order of added packages is preserved from the new lockfile."""
|
|
347
|
+
old_packages = []
|
|
348
|
+
new_packages = [
|
|
349
|
+
LockfilePackage(name="zebra", version="1.0.0"),
|
|
350
|
+
LockfilePackage(name="alpha", version="2.0.0"),
|
|
351
|
+
LockfilePackage(name="beta", version="3.0.0"),
|
|
352
|
+
]
|
|
353
|
+
old_lockfile = UvLockFile(
|
|
354
|
+
type=LockFileType.UV,
|
|
355
|
+
version=1,
|
|
356
|
+
revision=1,
|
|
357
|
+
**cast(Any, {"package": old_packages, "requires-python": ">=3.9"}),
|
|
358
|
+
)
|
|
359
|
+
new_lockfile = UvLockFile(
|
|
360
|
+
type=LockFileType.UV,
|
|
361
|
+
version=1,
|
|
362
|
+
revision=1,
|
|
363
|
+
**cast(Any, {"package": new_packages, "requires-python": ">=3.9"}),
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
reporter = LockFileReporter(
|
|
367
|
+
old_lockfile=old_lockfile,
|
|
368
|
+
new_lockfile=new_lockfile,
|
|
369
|
+
output_format=OutputFormat.TABLE,
|
|
370
|
+
show_learn_more_link=True,
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
changes = reporter.get_changes()
|
|
374
|
+
# Order should be preserved from new_packages
|
|
375
|
+
assert [pkg.name for pkg in changes.added] == ["zebra", "alpha", "beta"]
|
|
376
|
+
|
|
377
|
+
def test_cached_properties(self):
|
|
378
|
+
"""Test that cached properties work correctly."""
|
|
379
|
+
old_packages = [
|
|
380
|
+
LockfilePackage(name="pkg1", version="1.0.0"),
|
|
381
|
+
]
|
|
382
|
+
new_packages = [
|
|
383
|
+
LockfilePackage(name="pkg2", version="2.0.0"),
|
|
384
|
+
]
|
|
385
|
+
old_lockfile = UvLockFile(
|
|
386
|
+
type=LockFileType.UV,
|
|
387
|
+
version=1,
|
|
388
|
+
revision=1,
|
|
389
|
+
**cast(Any, {"package": old_packages, "requires-python": ">=3.9"}),
|
|
390
|
+
)
|
|
391
|
+
new_lockfile = UvLockFile(
|
|
392
|
+
type=LockFileType.UV,
|
|
393
|
+
version=1,
|
|
394
|
+
revision=1,
|
|
395
|
+
**cast(Any, {"package": new_packages, "requires-python": ">=3.9"}),
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
reporter = LockFileReporter(
|
|
399
|
+
old_lockfile=old_lockfile,
|
|
400
|
+
new_lockfile=new_lockfile,
|
|
401
|
+
output_format=OutputFormat.TABLE,
|
|
402
|
+
show_learn_more_link=False,
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
# Access cached properties multiple times
|
|
406
|
+
assert reporter.added_package_names == {"pkg2"}
|
|
407
|
+
assert reporter.added_package_names == {"pkg2"} # Should use cached value
|
|
408
|
+
|
|
409
|
+
assert reporter.removed_package_names == {"pkg1"}
|
|
410
|
+
assert reporter.removed_package_names == {"pkg1"} # Should use cached value
|
|
411
|
+
|
|
412
|
+
assert reporter.both_lockfile_package_names == set()
|
|
413
|
+
assert reporter.both_lockfile_package_names == set() # Should use cached value
|
|
414
|
+
|
|
415
|
+
def test_sort_packages_by_change_level(self):
|
|
416
|
+
"""Test that sort_packages_by_change_level returns packages sorted by change level (major first, then minor, then patch)."""
|
|
417
|
+
reporter = LockFileReporter(
|
|
418
|
+
old_lockfile=None,
|
|
419
|
+
new_lockfile=None,
|
|
420
|
+
output_format=OutputFormat.TABLE,
|
|
421
|
+
show_learn_more_link=False,
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
# Create packages with different change levels
|
|
425
|
+
major_update = UpdatedPackage(
|
|
426
|
+
name="major-pkg", old_version="1.0.0", new_version="2.0.0"
|
|
427
|
+
)
|
|
428
|
+
minor_update = UpdatedPackage(
|
|
429
|
+
name="minor-pkg", old_version="1.0.0", new_version="1.1.0"
|
|
430
|
+
)
|
|
431
|
+
patch_update = UpdatedPackage(
|
|
432
|
+
name="patch-pkg", old_version="1.0.0", new_version="1.0.1"
|
|
433
|
+
)
|
|
434
|
+
string_version_update = UpdatedPackage(
|
|
435
|
+
name="string-pkg", old_version="1.0.0.post0", new_version="1.0.1.post0"
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
# Test packages in mixed order
|
|
439
|
+
unsorted_packages = [
|
|
440
|
+
patch_update,
|
|
441
|
+
major_update,
|
|
442
|
+
string_version_update,
|
|
443
|
+
minor_update,
|
|
444
|
+
]
|
|
445
|
+
|
|
446
|
+
sorted_packages = reporter.sort_packages_by_change_level(unsorted_packages)
|
|
447
|
+
|
|
448
|
+
# Verify order: major first, then minor, then patch, then unknown (string versions)
|
|
449
|
+
assert len(sorted_packages) == 4
|
|
450
|
+
assert sorted_packages[0].name == "major-pkg" # MAJOR = 2
|
|
451
|
+
assert sorted_packages[1].name == "minor-pkg" # MINOR = 1
|
|
452
|
+
assert sorted_packages[2].name == "patch-pkg" # PATCH = 0
|
|
453
|
+
assert sorted_packages[3].name == "string-pkg" # UNKNOWN = -1
|
|
454
|
+
|
|
455
|
+
def test_sort_packages_by_change_level_same_level(self):
|
|
456
|
+
"""Test sorting when multiple packages have the same change level - should be alphabetical by name."""
|
|
457
|
+
reporter = LockFileReporter(
|
|
458
|
+
old_lockfile=None,
|
|
459
|
+
new_lockfile=None,
|
|
460
|
+
output_format=OutputFormat.TABLE,
|
|
461
|
+
show_learn_more_link=False,
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
# Create multiple packages with same change level but different names
|
|
465
|
+
major_update_zebra = UpdatedPackage(
|
|
466
|
+
name="zebra-pkg", old_version="1.0.0", new_version="2.0.0"
|
|
467
|
+
)
|
|
468
|
+
major_update_alpha = UpdatedPackage(
|
|
469
|
+
name="alpha-pkg", old_version="1.5.3", new_version="3.0.0"
|
|
470
|
+
)
|
|
471
|
+
major_update_beta = UpdatedPackage(
|
|
472
|
+
name="beta-pkg", old_version="2.0.0", new_version="5.0.0"
|
|
473
|
+
)
|
|
474
|
+
minor_update = UpdatedPackage(
|
|
475
|
+
name="minor-pkg", old_version="2.1.0", new_version="2.2.0"
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
# Pass in non-alphabetical order
|
|
479
|
+
packages = [
|
|
480
|
+
major_update_zebra,
|
|
481
|
+
minor_update,
|
|
482
|
+
major_update_beta,
|
|
483
|
+
major_update_alpha,
|
|
484
|
+
]
|
|
485
|
+
|
|
486
|
+
sorted_packages = reporter.sort_packages_by_change_level(packages)
|
|
487
|
+
|
|
488
|
+
# All major updates should come before minor update, and be sorted alphabetically
|
|
489
|
+
assert len(sorted_packages) == 4
|
|
490
|
+
assert sorted_packages[0].name == "alpha-pkg" # MAJOR, alphabetically first
|
|
491
|
+
assert sorted_packages[1].name == "beta-pkg" # MAJOR, alphabetically second
|
|
492
|
+
assert sorted_packages[2].name == "zebra-pkg" # MAJOR, alphabetically third
|
|
493
|
+
assert sorted_packages[3].name == "minor-pkg" # MINOR, comes after all major
|
|
494
|
+
|
|
495
|
+
def test_sort_packages_by_change_level_empty_list(self):
|
|
496
|
+
"""Test sorting an empty list of packages."""
|
|
497
|
+
reporter = LockFileReporter(
|
|
498
|
+
old_lockfile=None,
|
|
499
|
+
new_lockfile=None,
|
|
500
|
+
output_format=OutputFormat.TABLE,
|
|
501
|
+
show_learn_more_link=False,
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
sorted_packages = reporter.sort_packages_by_change_level([])
|
|
505
|
+
|
|
506
|
+
assert sorted_packages == []
|
|
507
|
+
|
|
508
|
+
def test_sort_packages_by_change_level_verifies_change_levels(self):
|
|
509
|
+
"""Test that the sorted packages have the correct change levels and names in correct order."""
|
|
510
|
+
reporter = LockFileReporter(
|
|
511
|
+
old_lockfile=None,
|
|
512
|
+
new_lockfile=None,
|
|
513
|
+
output_format=OutputFormat.TABLE,
|
|
514
|
+
show_learn_more_link=False,
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
# Create packages with all possible change levels
|
|
518
|
+
major_update = UpdatedPackage(
|
|
519
|
+
name="major-pkg", old_version="1.0.0", new_version="2.0.0"
|
|
520
|
+
)
|
|
521
|
+
minor_update = UpdatedPackage(
|
|
522
|
+
name="minor-pkg", old_version="1.0.0", new_version="1.1.0"
|
|
523
|
+
)
|
|
524
|
+
patch_update = UpdatedPackage(
|
|
525
|
+
name="patch-pkg", old_version="1.0.0", new_version="1.0.1"
|
|
526
|
+
)
|
|
527
|
+
post_patch_update = UpdatedPackage(
|
|
528
|
+
name="post-pkg", old_version="1.0.0.post0", new_version="1.0.1.post0"
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
packages = [patch_update, post_patch_update, major_update, minor_update]
|
|
532
|
+
|
|
533
|
+
sorted_packages = reporter.sort_packages_by_change_level(packages)
|
|
534
|
+
|
|
535
|
+
# Verify the change levels are in correct order (MAJOR=0, MINOR=1, PATCH=2)
|
|
536
|
+
# Note: .post0 versions are correctly parsed as PATCH changes by packaging.version
|
|
537
|
+
assert sorted_packages[0].change_level() == VersionChangeLevel.MAJOR # 0
|
|
538
|
+
assert sorted_packages[1].change_level() == VersionChangeLevel.MINOR # 1
|
|
539
|
+
assert sorted_packages[2].change_level() == VersionChangeLevel.PATCH # 2
|
|
540
|
+
assert sorted_packages[3].change_level() == VersionChangeLevel.PATCH # 2
|
|
541
|
+
|
|
542
|
+
def test_sort_packages_by_change_level_alphabetical_within_levels(self):
|
|
543
|
+
"""Test comprehensive sorting: by change level first, then alphabetically by name."""
|
|
544
|
+
reporter = LockFileReporter(
|
|
545
|
+
old_lockfile=None,
|
|
546
|
+
new_lockfile=None,
|
|
547
|
+
output_format=OutputFormat.TABLE,
|
|
548
|
+
show_learn_more_link=False,
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
# Create multiple packages at each change level with non-alphabetical names
|
|
552
|
+
packages = [
|
|
553
|
+
# PATCH updates
|
|
554
|
+
UpdatedPackage(
|
|
555
|
+
name="zulu-patch",
|
|
556
|
+
old_version="1.0.0",
|
|
557
|
+
new_version="1.0.1",
|
|
558
|
+
),
|
|
559
|
+
UpdatedPackage(
|
|
560
|
+
name="alpha-patch",
|
|
561
|
+
old_version="1.0.0",
|
|
562
|
+
new_version="1.0.2",
|
|
563
|
+
),
|
|
564
|
+
# MINOR updates
|
|
565
|
+
UpdatedPackage(
|
|
566
|
+
name="yankee-minor",
|
|
567
|
+
old_version="1.0.0",
|
|
568
|
+
new_version="1.1.0",
|
|
569
|
+
),
|
|
570
|
+
UpdatedPackage(
|
|
571
|
+
name="bravo-minor",
|
|
572
|
+
old_version="1.0.0",
|
|
573
|
+
new_version="1.2.0",
|
|
574
|
+
),
|
|
575
|
+
# MAJOR updates
|
|
576
|
+
UpdatedPackage(
|
|
577
|
+
name="whiskey-major",
|
|
578
|
+
old_version="1.0.0",
|
|
579
|
+
new_version="2.0.0",
|
|
580
|
+
),
|
|
581
|
+
UpdatedPackage(
|
|
582
|
+
name="charlie-major",
|
|
583
|
+
old_version="1.0.0",
|
|
584
|
+
new_version="3.0.0",
|
|
585
|
+
),
|
|
586
|
+
# POST PATCH updates (also PATCH level with packaging.version)
|
|
587
|
+
UpdatedPackage(
|
|
588
|
+
name="victor-post",
|
|
589
|
+
old_version="1.0.0.post0",
|
|
590
|
+
new_version="2.0.0.post0",
|
|
591
|
+
),
|
|
592
|
+
UpdatedPackage(
|
|
593
|
+
name="delta-post",
|
|
594
|
+
old_version="1.0.0.post0",
|
|
595
|
+
new_version="1.1.0.post0",
|
|
596
|
+
),
|
|
597
|
+
]
|
|
598
|
+
|
|
599
|
+
sorted_packages = reporter.sort_packages_by_change_level(packages)
|
|
600
|
+
|
|
601
|
+
# Verify correct order: MAJOR (alphabetical), MINOR (alphabetical), PATCH (alphabetical)
|
|
602
|
+
# Note: .post versions are now correctly parsed, so victor-post is MAJOR and delta-post is MINOR
|
|
603
|
+
expected_order = [
|
|
604
|
+
"charlie-major", # MAJOR, alphabetically first
|
|
605
|
+
"victor-post", # MAJOR (2.0.0.post0), alphabetically second
|
|
606
|
+
"whiskey-major", # MAJOR, alphabetically third
|
|
607
|
+
"bravo-minor", # MINOR, alphabetically first
|
|
608
|
+
"delta-post", # MINOR (1.1.0.post0), alphabetically second
|
|
609
|
+
"yankee-minor", # MINOR, alphabetically third
|
|
610
|
+
"alpha-patch", # PATCH, alphabetically first
|
|
611
|
+
"zulu-patch", # PATCH, alphabetically second
|
|
612
|
+
]
|
|
613
|
+
|
|
614
|
+
actual_order = [pkg.name for pkg in sorted_packages]
|
|
615
|
+
assert actual_order == expected_order
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from uv_lock_report.models import LockfilePackage
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class TestLockfilePackage:
|
|
5
|
+
def test_valid_version(self):
|
|
6
|
+
pkg_name = "pkg_name"
|
|
7
|
+
pkg_version = "1.2.0"
|
|
8
|
+
|
|
9
|
+
lfp = LockfilePackage(name=pkg_name, version=pkg_version)
|
|
10
|
+
|
|
11
|
+
assert lfp.name == pkg_name
|
|
12
|
+
assert lfp.version == pkg_version
|
|
13
|
+
|
|
14
|
+
def test_valid_version_from_dict(self):
|
|
15
|
+
pkg_name = "pkg_name"
|
|
16
|
+
pkg_version = "1.2.0"
|
|
17
|
+
|
|
18
|
+
lfp = LockfilePackage.model_validate(dict(name=pkg_name, version=pkg_version))
|
|
19
|
+
|
|
20
|
+
assert lfp.name == pkg_name
|
|
21
|
+
assert lfp.version == pkg_version
|
|
22
|
+
|
|
23
|
+
def test_major_version_only_from_dict(self):
|
|
24
|
+
d = {"name": "pkg_name", "version": "1"}
|
|
25
|
+
|
|
26
|
+
lfp = LockfilePackage.model_validate(d)
|
|
27
|
+
|
|
28
|
+
assert lfp.name == d["name"]
|
|
29
|
+
assert lfp.version == d["version"]
|
|
30
|
+
|
|
31
|
+
def test_major_minor_version_only_from_dict(self):
|
|
32
|
+
d = {"name": "pkg_name", "version": "1.2"}
|
|
33
|
+
|
|
34
|
+
lfp = LockfilePackage.model_validate(d)
|
|
35
|
+
|
|
36
|
+
assert lfp.name == d["name"]
|
|
37
|
+
assert lfp.version == d["version"]
|
|
38
|
+
|
|
39
|
+
def test_malformed_post_version(self):
|
|
40
|
+
## Python Dateutil does this
|
|
41
|
+
d = {"name": "pkg_name", "version": "2.9.0.post0"}
|
|
42
|
+
expected_version = "2.9.0.post0"
|
|
43
|
+
|
|
44
|
+
lfp = LockfilePackage.model_validate(d)
|
|
45
|
+
|
|
46
|
+
assert lfp.name == d["name"]
|
|
47
|
+
assert lfp.version == expected_version
|