uv-lock-report 0.11.4__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.

Potentially problematic release.


This version of uv-lock-report might be problematic. Click here for more details.

@@ -0,0 +1,324 @@
1
+ from unittest.mock import MagicMock, patch
2
+
3
+ import pytest
4
+
5
+ from uv_lock_report.models import UvLockFile
6
+ from uv_lock_report.report import get_new_uv_lock_file, get_old_uv_lock_file
7
+
8
+ # Sample minimal valid uv.lock content for testing
9
+ SAMPLE_UV_LOCK = """version = 1
10
+ revision = 3
11
+ requires-python = ">=3.13"
12
+
13
+ [[package]]
14
+ name = "test-package"
15
+ version = "1.0.0"
16
+ source = { registry = "https://pypi.org/simple" }
17
+ """
18
+
19
+ SAMPLE_UV_LOCK_UPDATED = """version = 1
20
+ revision = 3
21
+ requires-python = ">=3.13"
22
+
23
+ [[package]]
24
+ name = "test-package"
25
+ version = "2.0.0"
26
+ source = { registry = "https://pypi.org/simple" }
27
+
28
+ [[package]]
29
+ name = "new-package"
30
+ version = "1.5.0"
31
+ source = { registry = "https://pypi.org/simple" }
32
+ """
33
+
34
+
35
+ class TestGetNewUvLockFile:
36
+ """Test the get_new_uv_lock_file function for reading current uv.lock files."""
37
+
38
+ def test_lockfile_exists(self, tmp_path):
39
+ """Test when uv.lock exists in the base_path."""
40
+ # Create a uv.lock file in the temporary directory
41
+ uv_lock_path = tmp_path / "uv.lock"
42
+ uv_lock_path.write_text(SAMPLE_UV_LOCK)
43
+
44
+ # Call the function
45
+ result = get_new_uv_lock_file(str(tmp_path))
46
+
47
+ # Verify the result
48
+ assert result is not None
49
+ assert isinstance(result, UvLockFile)
50
+ assert result.version == 1
51
+ assert result.revision == 3
52
+ assert result.requires_python == ">=3.13"
53
+ assert len(result.packages) == 1
54
+ assert result.packages[0].name == "test-package"
55
+ assert str(result.packages[0].version) == "1.0.0"
56
+
57
+ def test_lockfile_not_exists(self, tmp_path, capsys):
58
+ """Test when uv.lock does not exist in the base_path."""
59
+ # Call the function without creating a uv.lock file
60
+ result = get_new_uv_lock_file(str(tmp_path))
61
+
62
+ # Verify the result
63
+ assert result is None
64
+
65
+ # Verify the error message was printed
66
+ captured = capsys.readouterr()
67
+ assert "uv.lock not found in current working directory" in captured.out
68
+
69
+ def test_lockfile_with_multiple_packages(self, tmp_path):
70
+ """Test parsing a lockfile with multiple packages."""
71
+ uv_lock_path = tmp_path / "uv.lock"
72
+ uv_lock_path.write_text(SAMPLE_UV_LOCK_UPDATED)
73
+
74
+ result = get_new_uv_lock_file(str(tmp_path))
75
+
76
+ assert result is not None
77
+ assert len(result.packages) == 2
78
+ package_names = {pkg.name for pkg in result.packages}
79
+ assert package_names == {"test-package", "new-package"}
80
+
81
+ def test_lockfile_with_relative_path(self, tmp_path):
82
+ """Test when base_path is a relative path."""
83
+ # Create a subdirectory
84
+ subdir = tmp_path / "subdir"
85
+ subdir.mkdir()
86
+ uv_lock_path = subdir / "uv.lock"
87
+ uv_lock_path.write_text(SAMPLE_UV_LOCK)
88
+
89
+ # Call with relative path components
90
+ result = get_new_uv_lock_file(str(subdir))
91
+
92
+ assert result is not None
93
+ assert isinstance(result, UvLockFile)
94
+
95
+ def test_lockfile_malformed_toml(self, tmp_path):
96
+ """Test when uv.lock contains malformed TOML."""
97
+ uv_lock_path = tmp_path / "uv.lock"
98
+ uv_lock_path.write_text("this is not valid toml {{{")
99
+
100
+ # Should raise an exception when parsing
101
+ with pytest.raises(Exception): # tomllib.TOMLDecodeError or similar
102
+ get_new_uv_lock_file(str(tmp_path))
103
+
104
+ def test_lockfile_empty(self, tmp_path):
105
+ """Test when uv.lock is empty."""
106
+ uv_lock_path = tmp_path / "uv.lock"
107
+ uv_lock_path.write_text("")
108
+
109
+ # Should raise an exception when parsing
110
+ with pytest.raises(Exception):
111
+ get_new_uv_lock_file(str(tmp_path))
112
+
113
+
114
+ class TestGetOldUvLockFile:
115
+ """Test the get_old_uv_lock_file function for reading historical uv.lock files via git."""
116
+
117
+ def test_git_show_success(self, tmp_path, capsys):
118
+ """Test when git show successfully retrieves the lockfile."""
119
+ base_sha = "abc123"
120
+
121
+ # Mock subprocess.run to return a successful result
122
+ mock_result = MagicMock()
123
+ mock_result.returncode = 0
124
+ mock_result.stdout = SAMPLE_UV_LOCK
125
+ mock_result.stderr = ""
126
+
127
+ with patch(
128
+ "uv_lock_report.report.subprocess.run", return_value=mock_result
129
+ ) as mock_run:
130
+ result = get_old_uv_lock_file(base_sha, str(tmp_path))
131
+
132
+ # Verify subprocess.run was called correctly
133
+ mock_run.assert_called_once_with(
134
+ ["git", "show", f"{base_sha}:uv.lock"],
135
+ capture_output=True,
136
+ text=True,
137
+ cwd=str(tmp_path),
138
+ )
139
+
140
+ # Verify the result
141
+ assert result is not None
142
+ assert isinstance(result, UvLockFile)
143
+ assert result.version == 1
144
+ assert len(result.packages) == 1
145
+ assert result.packages[0].name == "test-package"
146
+
147
+ # Verify success message was printed
148
+ captured = capsys.readouterr()
149
+ assert "Found uv.lock in base commit." in captured.out
150
+
151
+ def test_git_show_file_not_found(self, tmp_path, capsys):
152
+ """Test when git show fails because the file doesn't exist in the commit."""
153
+ base_sha = "abc123"
154
+
155
+ # Mock subprocess.run to return a failure
156
+ mock_result = MagicMock()
157
+ mock_result.returncode = 128
158
+ mock_result.stdout = ""
159
+ mock_result.stderr = "fatal: path 'uv.lock' does not exist in 'abc123'"
160
+ mock_result.args = ["git", "show", "abc123:uv.lock"]
161
+
162
+ with patch("uv_lock_report.report.subprocess.run", return_value=mock_result):
163
+ result = get_old_uv_lock_file(base_sha, str(tmp_path))
164
+
165
+ # Verify the result
166
+ assert result is None
167
+
168
+ # Verify error messages were printed
169
+ captured = capsys.readouterr()
170
+ assert "uv.lock not found in base commit" in captured.out
171
+ assert "fatal: path 'uv.lock' does not exist" in captured.out
172
+
173
+ def test_git_show_invalid_commit(self, tmp_path, capsys):
174
+ """Test when git show fails because the commit SHA is invalid."""
175
+ base_sha = "invalidsha"
176
+
177
+ # Mock subprocess.run to return a failure
178
+ mock_result = MagicMock()
179
+ mock_result.returncode = 128
180
+ mock_result.stdout = ""
181
+ mock_result.stderr = "fatal: invalid object name 'invalidsha'"
182
+ mock_result.args = ["git", "show", "invalidsha:uv.lock"]
183
+
184
+ with patch("uv_lock_report.report.subprocess.run", return_value=mock_result):
185
+ result = get_old_uv_lock_file(base_sha, str(tmp_path))
186
+
187
+ # Verify the result
188
+ assert result is None
189
+
190
+ # Verify error messages were printed
191
+ captured = capsys.readouterr()
192
+ assert "uv.lock not found in base commit" in captured.out
193
+
194
+ def test_git_show_with_different_sha_formats(self, tmp_path):
195
+ """Test with various SHA formats (full SHA, short SHA, branch name, tag)."""
196
+ test_cases = [
197
+ "a1b2c3d4e5f6", # Short SHA
198
+ "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0", # Full SHA
199
+ "main", # Branch name
200
+ "v1.0.0", # Tag
201
+ "HEAD~1", # Relative reference
202
+ ]
203
+
204
+ mock_result = MagicMock()
205
+ mock_result.returncode = 0
206
+ mock_result.stdout = SAMPLE_UV_LOCK
207
+
208
+ for sha in test_cases:
209
+ with patch(
210
+ "uv_lock_report.report.subprocess.run", return_value=mock_result
211
+ ) as mock_run:
212
+ result = get_old_uv_lock_file(sha, str(tmp_path))
213
+
214
+ # Verify subprocess.run was called with the correct SHA
215
+ mock_run.assert_called_once_with(
216
+ ["git", "show", f"{sha}:uv.lock"],
217
+ capture_output=True,
218
+ text=True,
219
+ cwd=str(tmp_path),
220
+ )
221
+
222
+ # Verify the result
223
+ assert result is not None
224
+
225
+ def test_git_show_with_updated_lockfile(self, tmp_path):
226
+ """Test parsing an updated lockfile from git."""
227
+ base_sha = "def456"
228
+
229
+ mock_result = MagicMock()
230
+ mock_result.returncode = 0
231
+ mock_result.stdout = SAMPLE_UV_LOCK_UPDATED
232
+
233
+ with patch("uv_lock_report.report.subprocess.run", return_value=mock_result):
234
+ result = get_old_uv_lock_file(base_sha, str(tmp_path))
235
+
236
+ assert result is not None
237
+ assert len(result.packages) == 2
238
+ package_names = {pkg.name for pkg in result.packages}
239
+ assert package_names == {"test-package", "new-package"}
240
+
241
+ def test_git_show_malformed_output(self, tmp_path):
242
+ """Test when git show returns malformed TOML."""
243
+ base_sha = "abc123"
244
+
245
+ mock_result = MagicMock()
246
+ mock_result.returncode = 0
247
+ mock_result.stdout = "this is not valid toml {[["
248
+
249
+ with patch("uv_lock_report.report.subprocess.run", return_value=mock_result):
250
+ # Should raise an exception when parsing
251
+ with pytest.raises(Exception):
252
+ get_old_uv_lock_file(base_sha, str(tmp_path))
253
+
254
+ def test_git_command_construction(self, tmp_path):
255
+ """Test that the git command is constructed correctly."""
256
+ base_sha = "test-sha"
257
+ base_path = str(tmp_path)
258
+
259
+ mock_result = MagicMock()
260
+ mock_result.returncode = 0
261
+ mock_result.stdout = SAMPLE_UV_LOCK
262
+
263
+ with patch(
264
+ "uv_lock_report.report.subprocess.run", return_value=mock_result
265
+ ) as mock_run:
266
+ get_old_uv_lock_file(base_sha, base_path)
267
+
268
+ # Verify the exact command structure
269
+ call_args = mock_run.call_args
270
+ assert call_args[0][0] == ["git", "show", f"{base_sha}:uv.lock"]
271
+ assert call_args[1]["capture_output"] is True
272
+ assert call_args[1]["text"] is True
273
+ assert call_args[1]["cwd"] == base_path
274
+
275
+
276
+ class TestGetLockfilesIntegration:
277
+ """Integration tests comparing get_new_uv_lock_file and get_old_uv_lock_file."""
278
+
279
+ def test_same_lockfile_content(self, tmp_path):
280
+ """Test that both functions can parse the same lockfile content."""
281
+ # Write current lockfile
282
+ uv_lock_path = tmp_path / "uv.lock"
283
+ uv_lock_path.write_text(SAMPLE_UV_LOCK)
284
+
285
+ # Mock git show to return the same content
286
+ mock_result = MagicMock()
287
+ mock_result.returncode = 0
288
+ mock_result.stdout = SAMPLE_UV_LOCK
289
+
290
+ new_lockfile = get_new_uv_lock_file(str(tmp_path))
291
+
292
+ with patch("uv_lock_report.report.subprocess.run", return_value=mock_result):
293
+ old_lockfile = get_old_uv_lock_file("abc123", str(tmp_path))
294
+
295
+ # Both should return valid lockfiles with identical content
296
+ assert new_lockfile is not None
297
+ assert old_lockfile is not None
298
+ assert new_lockfile.version == old_lockfile.version
299
+ assert new_lockfile.revision == old_lockfile.revision
300
+ assert len(new_lockfile.packages) == len(old_lockfile.packages)
301
+ assert new_lockfile.packages[0].name == old_lockfile.packages[0].name
302
+ assert new_lockfile.packages[0].version == old_lockfile.packages[0].version
303
+
304
+ def test_different_lockfile_content(self, tmp_path):
305
+ """Test that functions correctly handle different lockfile versions."""
306
+ # Write current lockfile
307
+ uv_lock_path = tmp_path / "uv.lock"
308
+ uv_lock_path.write_text(SAMPLE_UV_LOCK_UPDATED)
309
+
310
+ # Mock git show to return the old content
311
+ mock_result = MagicMock()
312
+ mock_result.returncode = 0
313
+ mock_result.stdout = SAMPLE_UV_LOCK
314
+
315
+ new_lockfile = get_new_uv_lock_file(str(tmp_path))
316
+
317
+ with patch("uv_lock_report.report.subprocess.run", return_value=mock_result):
318
+ old_lockfile = get_old_uv_lock_file("abc123", str(tmp_path))
319
+
320
+ # Both should be valid but with different content
321
+ assert new_lockfile is not None
322
+ assert old_lockfile is not None
323
+ assert len(new_lockfile.packages) == 2
324
+ assert len(old_lockfile.packages) == 1