|
26 | 26 | CreateDeploymentResponse, |
27 | 27 | DataTransformConfig, |
28 | 28 | DeploymentsResponse, |
| 29 | + _get_package_type_for_directory, |
29 | 30 | _make_api_call, |
30 | 31 | _retrieve_access_token, |
31 | 32 | _retrieve_access_token_from_sf_cli, |
@@ -92,7 +93,7 @@ def test_prepare_dependency_archive_image_exists( |
92 | 93 | mock_docker_build_cmd.return_value = "mock build command" |
93 | 94 | mock_docker_run_cmd.return_value = "mock run command" |
94 | 95 |
|
95 | | - prepare_dependency_archive("/test/dir", "default") |
| 96 | + prepare_dependency_archive("/test/dir", "default", "script") |
96 | 97 |
|
97 | 98 | # Verify docker images command was called |
98 | 99 | mock_cmd_output.assert_any_call(self.EXPECTED_DOCKER_IMAGES_CMD) |
@@ -152,7 +153,7 @@ def test_prepare_dependency_archive_build_image( |
152 | 153 | mock_docker_build_cmd.return_value = "mock build command" |
153 | 154 | mock_docker_run_cmd.return_value = "mock run command" |
154 | 155 |
|
155 | | - prepare_dependency_archive("/test/dir", "default") |
| 156 | + prepare_dependency_archive("/test/dir", "default", "script") |
156 | 157 |
|
157 | 158 | # Verify docker images command was called |
158 | 159 | mock_cmd_output.assert_any_call(self.EXPECTED_DOCKER_IMAGES_CMD) |
@@ -213,7 +214,7 @@ def test_prepare_dependency_archive_docker_build_failure( |
213 | 214 | ] |
214 | 215 |
|
215 | 216 | with pytest.raises(CalledProcessError, match="Build failed"): |
216 | | - prepare_dependency_archive("/test/dir", "default") |
| 217 | + prepare_dependency_archive("/test/dir", "default", "script") |
217 | 218 |
|
218 | 219 | # Verify docker images command was called |
219 | 220 | mock_cmd_output.assert_any_call(self.EXPECTED_DOCKER_IMAGES_CMD) |
@@ -256,7 +257,7 @@ def test_prepare_dependency_archive_docker_run_failure( |
256 | 257 | ] |
257 | 258 |
|
258 | 259 | with pytest.raises(CalledProcessError, match="Run failed"): |
259 | | - prepare_dependency_archive("/test/dir", "default") |
| 260 | + prepare_dependency_archive("/test/dir", "default", "script") |
260 | 261 |
|
261 | 262 | # Verify docker images command was called |
262 | 263 | mock_cmd_output.assert_any_call(self.EXPECTED_DOCKER_IMAGES_CMD) |
@@ -299,14 +300,211 @@ def test_prepare_dependency_archive_file_copy_failure( |
299 | 300 | mock_copy.side_effect = FileNotFoundError("File not found") |
300 | 301 |
|
301 | 302 | with pytest.raises(FileNotFoundError, match="File not found"): |
302 | | - prepare_dependency_archive("/test/dir", "default") |
| 303 | + prepare_dependency_archive("/test/dir", "default", "script") |
303 | 304 |
|
304 | 305 | # Verify docker images command was called |
305 | 306 | mock_cmd_output.assert_any_call(self.EXPECTED_DOCKER_IMAGES_CMD) |
306 | 307 |
|
307 | 308 | # Verify files were attempted to be copied |
308 | 309 | mock_copy.assert_any_call("requirements.txt", "/tmp/test_dir") |
309 | 310 |
|
| 311 | + @patch("datacustomcode.deploy.cmd_output") |
| 312 | + @patch("datacustomcode.deploy.shutil.copytree") |
| 313 | + @patch("datacustomcode.deploy.shutil.rmtree") |
| 314 | + @patch("datacustomcode.deploy.shutil.copy") |
| 315 | + @patch("datacustomcode.deploy.tempfile.TemporaryDirectory") |
| 316 | + @patch("datacustomcode.deploy.os.path.exists") |
| 317 | + @patch("datacustomcode.deploy.os.path.join") |
| 318 | + @patch("datacustomcode.deploy.os.makedirs") |
| 319 | + @patch("datacustomcode.deploy.docker_build_cmd") |
| 320 | + @patch("datacustomcode.deploy.docker_run_cmd") |
| 321 | + def test_prepare_dependency_archive_function_type( |
| 322 | + self, |
| 323 | + mock_docker_run_cmd, |
| 324 | + mock_docker_build_cmd, |
| 325 | + mock_makedirs, |
| 326 | + mock_join, |
| 327 | + mock_exists, |
| 328 | + mock_temp_dir, |
| 329 | + mock_copy, |
| 330 | + mock_rmtree, |
| 331 | + mock_copytree, |
| 332 | + mock_cmd_output, |
| 333 | + ): |
| 334 | + """Test prepare_dependency_archive with function package type.""" |
| 335 | + # Mock the temporary directory context manager |
| 336 | + mock_temp_dir_instance = MagicMock() |
| 337 | + mock_temp_dir_instance.__enter__.return_value = "/tmp/test_dir" |
| 338 | + mock_temp_dir_instance.__exit__.return_value = None |
| 339 | + mock_temp_dir.return_value = mock_temp_dir_instance |
| 340 | + |
| 341 | + # Mock cmd_output to return image ID (indicating image exists) |
| 342 | + mock_cmd_output.return_value = "abc123" |
| 343 | + |
| 344 | + # Mock os.path.join for py-files paths |
| 345 | + def join_side_effect(*args): |
| 346 | + if args == ("/tmp/test_dir", "py-files"): |
| 347 | + return "/tmp/test_dir/py-files" |
| 348 | + return "/".join(args) |
| 349 | + |
| 350 | + mock_join.side_effect = join_side_effect |
| 351 | + |
| 352 | + # Mock os.path.exists |
| 353 | + def exists_side_effect(path): |
| 354 | + if path == "/tmp/test_dir/py-files": |
| 355 | + return True |
| 356 | + if path == "payload/py-files": |
| 357 | + return False |
| 358 | + return False |
| 359 | + |
| 360 | + mock_exists.side_effect = exists_side_effect |
| 361 | + |
| 362 | + # Mock the docker command functions |
| 363 | + mock_docker_build_cmd.return_value = "mock build command" |
| 364 | + mock_docker_run_cmd.return_value = "mock run command" |
| 365 | + |
| 366 | + prepare_dependency_archive("/test/dir", "default", "function") |
| 367 | + |
| 368 | + # Verify docker images command was called |
| 369 | + mock_cmd_output.assert_any_call(self.EXPECTED_DOCKER_IMAGES_CMD) |
| 370 | + |
| 371 | + # Verify docker build command was not called (since image already exists) |
| 372 | + mock_docker_build_cmd.assert_not_called() |
| 373 | + |
| 374 | + # Verify files were copied to temp directory |
| 375 | + mock_copy.assert_any_call("requirements.txt", "/tmp/test_dir") |
| 376 | + mock_copy.assert_any_call("build_native_dependencies.sh", "/tmp/test_dir") |
| 377 | + |
| 378 | + # Verify docker run command was called |
| 379 | + mock_docker_run_cmd.assert_called_once_with("default", "/tmp/test_dir") |
| 380 | + mock_cmd_output.assert_any_call("mock run command") |
| 381 | + |
| 382 | + # Verify payload directory was created |
| 383 | + mock_makedirs.assert_called_once_with("payload", exist_ok=True) |
| 384 | + |
| 385 | + # Verify py-files was NOT removed (doesn't exist yet) |
| 386 | + mock_rmtree.assert_not_called() |
| 387 | + |
| 388 | + # Verify py-files directory was copied |
| 389 | + mock_copytree.assert_called_once_with( |
| 390 | + "/tmp/test_dir/py-files", "payload/py-files" |
| 391 | + ) |
| 392 | + |
| 393 | + @patch("datacustomcode.deploy.cmd_output") |
| 394 | + @patch("datacustomcode.deploy.shutil.copy") |
| 395 | + @patch("datacustomcode.deploy.tempfile.TemporaryDirectory") |
| 396 | + @patch("datacustomcode.deploy.os.path.exists") |
| 397 | + @patch("datacustomcode.deploy.os.path.join") |
| 398 | + @patch("datacustomcode.deploy.os.makedirs") |
| 399 | + @patch("datacustomcode.deploy.docker_build_cmd") |
| 400 | + @patch("datacustomcode.deploy.docker_run_cmd") |
| 401 | + def test_prepare_dependency_archive_function_type_missing_pyfiles( |
| 402 | + self, |
| 403 | + mock_docker_run_cmd, |
| 404 | + mock_docker_build_cmd, |
| 405 | + mock_makedirs, |
| 406 | + mock_join, |
| 407 | + mock_exists, |
| 408 | + mock_temp_dir, |
| 409 | + mock_copy, |
| 410 | + mock_cmd_output, |
| 411 | + ): |
| 412 | + """Test prepare_dependency_archive with function type when py-files is missing.""" |
| 413 | + # Mock the temporary directory context manager |
| 414 | + mock_temp_dir_instance = MagicMock() |
| 415 | + mock_temp_dir_instance.__enter__.return_value = "/tmp/test_dir" |
| 416 | + mock_temp_dir_instance.__exit__.return_value = None |
| 417 | + mock_temp_dir.return_value = mock_temp_dir_instance |
| 418 | + |
| 419 | + # Mock cmd_output to return image ID (indicating image exists) |
| 420 | + mock_cmd_output.return_value = "abc123" |
| 421 | + |
| 422 | + # Mock os.path.join for py-files path |
| 423 | + def join_side_effect(*args): |
| 424 | + if args == ("/tmp/test_dir", "py-files"): |
| 425 | + return "/tmp/test_dir/py-files" |
| 426 | + return "/".join(args) |
| 427 | + |
| 428 | + mock_join.side_effect = join_side_effect |
| 429 | + |
| 430 | + # Mock os.path.exists to return False for py-files (doesn't exist) |
| 431 | + mock_exists.return_value = False |
| 432 | + |
| 433 | + # Mock the docker command functions |
| 434 | + mock_docker_build_cmd.return_value = "mock build command" |
| 435 | + mock_docker_run_cmd.return_value = "mock run command" |
| 436 | + |
| 437 | + # Should raise FileNotFoundError when py-files doesn't exist |
| 438 | + with pytest.raises( |
| 439 | + FileNotFoundError, |
| 440 | + match="Expected py-files directory not found at /tmp/test_dir/py-files", |
| 441 | + ): |
| 442 | + prepare_dependency_archive("/test/dir", "default", "function") |
| 443 | + |
| 444 | + # Verify docker commands were still called |
| 445 | + mock_cmd_output.assert_any_call(self.EXPECTED_DOCKER_IMAGES_CMD) |
| 446 | + mock_docker_run_cmd.assert_called_once_with("default", "/tmp/test_dir") |
| 447 | + |
| 448 | + |
| 449 | +class TestGetPackageTypeForDirectory: |
| 450 | + @patch("datacustomcode.deploy.get_package_type") |
| 451 | + @patch("datacustomcode.deploy.find_base_directory") |
| 452 | + def test_get_package_type_function( |
| 453 | + self, mock_find_base, mock_get_package_type |
| 454 | + ): |
| 455 | + """Test _get_package_type_for_directory returns function type.""" |
| 456 | + mock_find_base.return_value = "/project/root" |
| 457 | + mock_get_package_type.return_value = "function" |
| 458 | + |
| 459 | + result = _get_package_type_for_directory("/project/root/payload") |
| 460 | + |
| 461 | + assert result == "function" |
| 462 | + mock_find_base.assert_called_once() |
| 463 | + mock_get_package_type.assert_called_once_with("/project/root") |
| 464 | + |
| 465 | + @patch("datacustomcode.deploy.get_package_type") |
| 466 | + @patch("datacustomcode.deploy.find_base_directory") |
| 467 | + def test_get_package_type_script(self, mock_find_base, mock_get_package_type): |
| 468 | + """Test _get_package_type_for_directory returns script type.""" |
| 469 | + mock_find_base.return_value = "/project/root" |
| 470 | + mock_get_package_type.return_value = "script" |
| 471 | + |
| 472 | + result = _get_package_type_for_directory("/project/root/payload") |
| 473 | + |
| 474 | + assert result == "script" |
| 475 | + mock_find_base.assert_called_once() |
| 476 | + mock_get_package_type.assert_called_once_with("/project/root") |
| 477 | + |
| 478 | + @patch("datacustomcode.deploy.get_package_type") |
| 479 | + @patch("datacustomcode.deploy.find_base_directory") |
| 480 | + def test_get_package_type_file_not_found( |
| 481 | + self, mock_find_base, mock_get_package_type |
| 482 | + ): |
| 483 | + """Test _get_package_type_for_directory defaults to script when config not found.""" |
| 484 | + mock_find_base.return_value = "/project/root" |
| 485 | + mock_get_package_type.side_effect = FileNotFoundError("Config not found") |
| 486 | + |
| 487 | + result = _get_package_type_for_directory("/project/root/payload") |
| 488 | + |
| 489 | + assert result == "script" |
| 490 | + mock_find_base.assert_called_once() |
| 491 | + mock_get_package_type.assert_called_once_with("/project/root") |
| 492 | + |
| 493 | + @patch("datacustomcode.deploy.get_package_type") |
| 494 | + @patch("datacustomcode.deploy.find_base_directory") |
| 495 | + def test_get_package_type_value_error( |
| 496 | + self, mock_find_base, mock_get_package_type |
| 497 | + ): |
| 498 | + """Test _get_package_type_for_directory defaults to script on ValueError.""" |
| 499 | + mock_find_base.return_value = "/project/root" |
| 500 | + mock_get_package_type.side_effect = ValueError("Invalid type") |
| 501 | + |
| 502 | + result = _get_package_type_for_directory("/project/root/payload") |
| 503 | + |
| 504 | + assert result == "script" |
| 505 | + mock_find_base.assert_called_once() |
| 506 | + mock_get_package_type.assert_called_once_with("/project/root") |
| 507 | + |
310 | 508 |
|
311 | 509 | class TestHasNonemptyRequirementsFile: |
312 | 510 | @patch("datacustomcode.deploy.os.path.dirname") |
@@ -645,6 +843,42 @@ def test_zip_without_requirements( |
645 | 843 | ) |
646 | 844 | assert mock_zipfile_instance.write.call_count == 3 # One call per file |
647 | 845 |
|
| 846 | + @patch("datacustomcode.deploy._get_package_type_for_directory") |
| 847 | + @patch("datacustomcode.deploy.has_nonempty_requirements_file") |
| 848 | + @patch("datacustomcode.deploy.prepare_dependency_archive") |
| 849 | + @patch("zipfile.ZipFile") |
| 850 | + @patch("os.walk") |
| 851 | + def test_zip_with_function_package_type( |
| 852 | + self, |
| 853 | + mock_walk, |
| 854 | + mock_zipfile, |
| 855 | + mock_prepare, |
| 856 | + mock_has_requirements, |
| 857 | + mock_get_package_type, |
| 858 | + ): |
| 859 | + """Test zipping a directory with function package type.""" |
| 860 | + mock_has_requirements.return_value = True |
| 861 | + mock_get_package_type.return_value = "function" |
| 862 | + mock_zipfile_instance = MagicMock() |
| 863 | + mock_zipfile.return_value.__enter__.return_value = mock_zipfile_instance |
| 864 | + mock_zipfile_instance.write = MagicMock() |
| 865 | + |
| 866 | + # Mock os.walk to return some test files |
| 867 | + mock_walk.return_value = [ |
| 868 | + ("/test/dir", ["subdir"], ["file1.py", "file2.py"]), |
| 869 | + ("/test/dir/subdir", [], ["file3.py"]), |
| 870 | + ] |
| 871 | + |
| 872 | + zip("/test/dir", "default") |
| 873 | + |
| 874 | + mock_has_requirements.assert_called_once_with("/test/dir") |
| 875 | + mock_get_package_type.assert_called_once_with("/test/dir") |
| 876 | + mock_prepare.assert_called_once_with("/test/dir", "default", "function") |
| 877 | + mock_zipfile.assert_called_once_with( |
| 878 | + "deployment.zip", "w", zipfile.ZIP_DEFLATED |
| 879 | + ) |
| 880 | + assert mock_zipfile_instance.write.call_count == 3 # One call per file |
| 881 | + |
648 | 882 |
|
649 | 883 | class TestUploadZip: |
650 | 884 | @patch("datacustomcode.deploy.requests.put") |
|
0 commit comments