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.
@@ -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