Skip to content

Commit bc35d5d

Browse files
committed
Unit test updates
1 parent 83b4af1 commit bc35d5d

File tree

2 files changed

+223
-13
lines changed

2 files changed

+223
-13
lines changed

tests/test_deploy.py

Lines changed: 94 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
mock_open,
66
patch,
77
)
8+
import zipfile
89

910
import pytest
1011
import requests
@@ -28,9 +29,10 @@
2829
get_data_transform_config,
2930
get_deployments,
3031
run_data_transform,
32+
upload_zip,
3133
verify_data_transform_config,
3234
wait_for_deployment,
33-
zip_and_upload_directory,
35+
zip,
3436
)
3537

3638

@@ -134,22 +136,100 @@ def test_create_deployment_conflict(self, mock_make_api_call):
134136
create_deployment(access_token, metadata)
135137

136138

137-
class TestZipAndUploadDirectory:
138-
@patch("datacustomcode.deploy.shutil.make_archive")
139+
class TestZip:
140+
@patch("datacustomcode.deploy.has_nonempty_requirements_file")
141+
@patch("datacustomcode.deploy.prepare_dependency_archive")
142+
@patch("zipfile.ZipFile")
143+
@patch("os.walk")
144+
def test_zip_with_requirements(
145+
self, mock_walk, mock_zipfile, mock_prepare, mock_has_requirements
146+
):
147+
"""Test zipping a directory with requirements.txt."""
148+
mock_has_requirements.return_value = True
149+
mock_zipfile_instance = MagicMock()
150+
mock_zipfile.return_value.__enter__.return_value = mock_zipfile_instance
151+
mock_zipfile_instance.write = MagicMock()
152+
153+
# Mock os.walk to return some test files
154+
mock_walk.return_value = [
155+
("/test/dir", ["subdir"], ["file1.py", "file2.py"]),
156+
("/test/dir/subdir", [], ["file3.py"]),
157+
]
158+
159+
zip("/test/dir")
160+
161+
mock_has_requirements.assert_called_once_with("/test/dir")
162+
mock_prepare.assert_called_once_with("/test/dir")
163+
mock_zipfile.assert_called_once_with(
164+
"deployment.zip", "w", zipfile.ZIP_DEFLATED
165+
)
166+
assert mock_zipfile_instance.write.call_count == 3 # One call per file
167+
168+
@patch("datacustomcode.deploy.has_nonempty_requirements_file")
169+
@patch("datacustomcode.deploy.prepare_dependency_archive")
170+
@patch("zipfile.ZipFile")
171+
@patch("os.walk")
172+
def test_zip_without_requirements(
173+
self, mock_walk, mock_zipfile, mock_prepare, mock_has_requirements
174+
):
175+
"""Test zipping a directory without requirements.txt."""
176+
mock_has_requirements.return_value = False
177+
mock_zipfile_instance = MagicMock()
178+
mock_zipfile.return_value.__enter__.return_value = mock_zipfile_instance
179+
mock_zipfile_instance.write = MagicMock()
180+
181+
# Mock os.walk to return some test files
182+
mock_walk.return_value = [
183+
("/test/dir", ["subdir"], ["file1.py", "file2.py"]),
184+
("/test/dir/subdir", [], ["file3.py"]),
185+
]
186+
187+
zip("/test/dir")
188+
189+
mock_has_requirements.assert_called_once_with("/test/dir")
190+
mock_prepare.assert_not_called()
191+
mock_zipfile.assert_called_once_with(
192+
"deployment.zip", "w", zipfile.ZIP_DEFLATED
193+
)
194+
assert mock_zipfile_instance.write.call_count == 3 # One call per file
195+
196+
197+
class TestUploadZip:
139198
@patch("datacustomcode.deploy.requests.put")
140199
@patch("builtins.open", new_callable=mock_open, read_data=b"test data")
141-
def test_zip_and_upload_directory(self, mock_file, mock_put, mock_make_archive):
142-
"""Test zipping and uploading a directory."""
200+
def test_upload_zip_success(self, mock_file, mock_put):
201+
"""Test successful zip upload."""
143202
mock_response = MagicMock()
144203
mock_put.return_value = mock_response
145204

146-
zip_and_upload_directory("/test/dir", "https://upload.example.com")
205+
upload_zip("https://upload.example.com")
147206

148-
mock_make_archive.assert_called_once_with("deployment", "zip", "/test/dir")
149207
mock_file.assert_called_once_with("deployment.zip", "rb")
150-
mock_put.assert_called_once()
208+
mock_put.assert_called_once_with(
209+
"https://upload.example.com",
210+
data=mock_file.return_value,
211+
headers={"Content-Type": "application/zip"},
212+
)
151213
mock_response.raise_for_status.assert_called_once()
152214

215+
@patch("datacustomcode.deploy.requests.put")
216+
@patch("builtins.open", new_callable=mock_open, read_data=b"test data")
217+
def test_upload_zip_http_error(self, mock_file, mock_put):
218+
"""Test zip upload with HTTP error."""
219+
mock_response = MagicMock()
220+
mock_response.raise_for_status.side_effect = requests.HTTPError("Upload failed")
221+
mock_put.return_value = mock_response
222+
223+
with pytest.raises(requests.HTTPError, match="Upload failed"):
224+
upload_zip("https://upload.example.com")
225+
226+
mock_file.assert_called_once_with("deployment.zip", "rb")
227+
mock_put.assert_called_once_with(
228+
"https://upload.example.com",
229+
data=mock_file.return_value,
230+
headers={"Content-Type": "application/zip"},
231+
)
232+
153233

154234
class TestGetDeployments:
155235
@patch("datacustomcode.deploy._make_api_call")
@@ -328,14 +408,16 @@ class TestDeployFull:
328408
@patch("datacustomcode.deploy.prepare_dependency_archive")
329409
@patch("datacustomcode.deploy.verify_data_transform_config")
330410
@patch("datacustomcode.deploy.create_deployment")
331-
@patch("datacustomcode.deploy.zip_and_upload_directory")
411+
@patch("datacustomcode.deploy.zip")
412+
@patch("datacustomcode.deploy.upload_zip")
332413
@patch("datacustomcode.deploy.wait_for_deployment")
333414
@patch("datacustomcode.deploy.create_data_transform")
334415
def test_deploy_full(
335416
self,
336417
mock_create_transform,
337418
mock_wait,
338-
mock_zip_upload,
419+
mock_upload_zip,
420+
mock_zip,
339421
mock_create_deployment,
340422
mock_verify_config,
341423
mock_prepare,
@@ -371,9 +453,8 @@ def test_deploy_full(
371453
mock_prepare.assert_called_once_with("/test/dir")
372454
mock_verify_config.assert_called_once_with("/test/dir")
373455
mock_create_deployment.assert_called_once_with(access_token, metadata)
374-
mock_zip_upload.assert_called_once_with(
375-
"/test/dir", "https://upload.example.com"
376-
)
456+
mock_zip.assert_called_once_with("/test/dir")
457+
mock_upload_zip.assert_called_once_with("https://upload.example.com")
377458
mock_wait.assert_called_once_with(access_token, metadata, callback)
378459
mock_create_transform.assert_called_once_with(
379460
"/test/dir", access_token, metadata

tests/test_scan.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
DataAccessLayerCalls,
1212
dc_config_json_from_file,
1313
scan_file,
14+
scan_file_for_imports,
15+
write_requirements_file,
1416
)
1517

1618

@@ -422,3 +424,130 @@ def process_customer_data():
422424
assert config["permissions"]["write"]["dlo"] == ["customer_data_enriched"]
423425
finally:
424426
os.unlink(temp_path)
427+
428+
429+
class TestRequirementsFile:
430+
def test_scan_file_for_imports(self):
431+
"""Test scanning a file for external package imports."""
432+
content = textwrap.dedent(
433+
"""
434+
import pandas as pd
435+
import numpy as np
436+
from sklearn.linear_model import LinearRegression
437+
import os # Standard library
438+
import sys # Standard library
439+
from datacustomcode.client import Client # Internal package
440+
"""
441+
)
442+
temp_path = create_test_script(content)
443+
try:
444+
imports = scan_file_for_imports(temp_path)
445+
assert "pandas" in imports
446+
assert "numpy" in imports
447+
assert "sklearn" in imports
448+
assert "os" not in imports # Standard library
449+
assert "sys" not in imports # Standard library
450+
assert "datacustomcode" not in imports # Internal package
451+
finally:
452+
os.unlink(temp_path)
453+
454+
def test_write_requirements_file_new(self):
455+
"""Test writing a new requirements.txt file."""
456+
content = textwrap.dedent(
457+
"""
458+
import pandas as pd
459+
import numpy as np
460+
"""
461+
)
462+
temp_path = create_test_script(content)
463+
try:
464+
requirements_path = write_requirements_file(temp_path)
465+
assert os.path.exists(requirements_path)
466+
467+
with open(requirements_path, "r") as f:
468+
requirements = {line.strip() for line in f}
469+
470+
assert "pandas" in requirements
471+
assert "numpy" in requirements
472+
finally:
473+
os.unlink(temp_path)
474+
if os.path.exists(requirements_path):
475+
os.unlink(requirements_path)
476+
477+
def test_write_requirements_file_merge(self):
478+
"""Test merging with existing requirements.txt file."""
479+
# First create an existing requirements.txt
480+
temp_dir = tempfile.mkdtemp()
481+
existing_requirements = os.path.join(temp_dir, "requirements.txt")
482+
with open(existing_requirements, "w") as f:
483+
f.write("pandas\nnumpy\n")
484+
485+
# Create a new Python file with additional imports
486+
content = textwrap.dedent(
487+
"""
488+
import pandas as pd
489+
import numpy as np
490+
import scipy
491+
import matplotlib
492+
"""
493+
)
494+
temp_path = create_test_script(content)
495+
try:
496+
requirements_path = write_requirements_file(temp_path)
497+
assert os.path.exists(requirements_path)
498+
499+
with open(requirements_path, "r") as f:
500+
requirements = {line.strip() for line in f}
501+
502+
# Check that both existing and new requirements are present
503+
assert "pandas" in requirements
504+
assert "numpy" in requirements
505+
assert "scipy" in requirements
506+
assert "matplotlib" in requirements
507+
finally:
508+
os.unlink(temp_path)
509+
if os.path.exists(requirements_path):
510+
os.unlink(requirements_path)
511+
if os.path.exists(existing_requirements):
512+
os.unlink(existing_requirements)
513+
os.rmdir(temp_dir)
514+
515+
def test_standard_library_exclusion(self):
516+
"""Test that standard library imports are excluded."""
517+
content = textwrap.dedent(
518+
"""
519+
import os
520+
import sys
521+
import json
522+
import datetime
523+
import pandas as pd
524+
"""
525+
)
526+
temp_path = create_test_script(content)
527+
try:
528+
imports = scan_file_for_imports(temp_path)
529+
assert "pandas" in imports
530+
assert "os" not in imports
531+
assert "sys" not in imports
532+
assert "json" not in imports
533+
assert "datetime" not in imports
534+
finally:
535+
os.unlink(temp_path)
536+
537+
def test_excluded_packages(self):
538+
"""Test that excluded packages are not included in requirements."""
539+
content = textwrap.dedent(
540+
"""
541+
import datacustomcode
542+
import pyspark
543+
import pandas as pd
544+
"""
545+
)
546+
temp_path = create_test_script(content)
547+
try:
548+
imports = scan_file_for_imports(temp_path)
549+
assert "pandas" in imports
550+
assert "datacustomcode" not in imports
551+
assert "pyspark" not in imports
552+
finally:
553+
os.unlink(temp_path)

0 commit comments

Comments
 (0)