@@ -828,5 +828,175 @@ def test_ghe_without_github_token_falls_back(self):
828828 assert 'ado-token' not in url
829829
830830
831+ class TestSubdirectoryPackageCommitSHA :
832+ """Test commit SHA handling in download_subdirectory_package."""
833+
834+ def setup_method (self ):
835+ self .temp_dir = Path (tempfile .mkdtemp ())
836+
837+ def teardown_method (self ):
838+ if self .temp_dir .exists ():
839+ shutil .rmtree (self .temp_dir , ignore_errors = True )
840+
841+ def _make_dep_ref (self , ref = None ):
842+ """Create a virtual subdirectory DependencyReference."""
843+ dep = DependencyReference (
844+ repo_url = 'owner/monorepo' ,
845+ host = 'github.com' ,
846+ reference = ref ,
847+ virtual_path = 'packages/my-skill' ,
848+ is_virtual = True ,
849+ )
850+ return dep
851+
852+ @patch ('apm_cli.deps.github_downloader.Repo' )
853+ @patch ('apm_cli.deps.github_downloader.validate_apm_package' )
854+ def test_sha_ref_clones_without_depth_and_checks_out (self , mock_validate , mock_repo_class ):
855+ """Commit SHA refs must clone with no_checkout (no depth/branch) then checkout the SHA."""
856+ sha = 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2'
857+ dep_ref = self ._make_dep_ref (ref = sha )
858+
859+ mock_repo = Mock ()
860+ mock_repo_class .return_value = mock_repo
861+
862+ mock_validation = ValidationResult ()
863+ mock_validation .is_valid = True
864+ mock_validation .package = APMPackage (name = 'my-skill' , version = '1.0.0' )
865+ mock_validate .return_value = mock_validation
866+
867+ with patch .dict (os .environ , {}, clear = True ):
868+ downloader = GitHubPackageDownloader ()
869+
870+ with patch .object (downloader , '_clone_with_fallback' ) as mock_clone :
871+ mock_clone .return_value = mock_repo
872+
873+ target = self .temp_dir / 'my-skill'
874+
875+ # Create the subdirectory structure that download_subdirectory_package expects
876+ def setup_subdir (* args , ** kwargs ):
877+ clone_path = args [1 ]
878+ subdir = clone_path / 'packages' / 'my-skill'
879+ subdir .mkdir (parents = True )
880+ (subdir / 'apm.yml' ).write_text ('name: my-skill\n version: 1.0.0\n ' )
881+ return mock_repo
882+
883+ mock_clone .side_effect = setup_subdir
884+
885+ downloader .download_subdirectory_package (dep_ref , target )
886+
887+ # Verify clone was called without depth/branch but WITH no_checkout
888+ call_kwargs = mock_clone .call_args
889+ assert 'depth' not in call_kwargs .kwargs , "SHA ref should NOT use shallow clone"
890+ assert 'branch' not in call_kwargs .kwargs , "SHA ref should NOT pass branch"
891+ assert call_kwargs .kwargs .get ('no_checkout' ) is True , "SHA ref should use no_checkout=True"
892+
893+ # Verify checkout was called with the SHA
894+ mock_repo .git .checkout .assert_called_once_with (sha )
895+
896+ @patch ('apm_cli.deps.github_downloader.Repo' )
897+ @patch ('apm_cli.deps.github_downloader.validate_apm_package' )
898+ def test_branch_ref_uses_shallow_clone (self , mock_validate , mock_repo_class ):
899+ """Branch/tag refs must use shallow clone with depth=1 and branch kwarg."""
900+ dep_ref = self ._make_dep_ref (ref = 'main' )
901+
902+ mock_repo = Mock ()
903+ mock_repo_class .return_value = mock_repo
904+
905+ mock_validation = ValidationResult ()
906+ mock_validation .is_valid = True
907+ mock_validation .package = APMPackage (name = 'my-skill' , version = '1.0.0' )
908+ mock_validate .return_value = mock_validation
909+
910+ with patch .dict (os .environ , {}, clear = True ):
911+ downloader = GitHubPackageDownloader ()
912+
913+ with patch .object (downloader , '_clone_with_fallback' ) as mock_clone :
914+ mock_clone .return_value = mock_repo
915+
916+ target = self .temp_dir / 'my-skill'
917+
918+ def setup_subdir (* args , ** kwargs ):
919+ clone_path = args [1 ]
920+ subdir = clone_path / 'packages' / 'my-skill'
921+ subdir .mkdir (parents = True )
922+ (subdir / 'apm.yml' ).write_text ('name: my-skill\n version: 1.0.0\n ' )
923+ return mock_repo
924+
925+ mock_clone .side_effect = setup_subdir
926+
927+ downloader .download_subdirectory_package (dep_ref , target )
928+
929+ call_kwargs = mock_clone .call_args
930+ assert call_kwargs .kwargs .get ('depth' ) == 1 , "Branch ref should use depth=1"
931+ assert call_kwargs .kwargs .get ('branch' ) == 'main' , "Branch ref should pass branch"
932+ assert 'no_checkout' not in call_kwargs .kwargs , "Branch ref should not set no_checkout"
933+
934+ # No explicit checkout for branch refs
935+ mock_repo .git .checkout .assert_not_called ()
936+
937+ @patch ('apm_cli.deps.github_downloader.Repo' )
938+ @patch ('apm_cli.deps.github_downloader.validate_apm_package' )
939+ def test_no_ref_uses_shallow_clone_without_branch (self , mock_validate , mock_repo_class ):
940+ """No ref should use shallow clone without branch kwarg (default branch)."""
941+ dep_ref = self ._make_dep_ref (ref = None )
942+
943+ mock_repo = Mock ()
944+ mock_repo_class .return_value = mock_repo
945+
946+ mock_validation = ValidationResult ()
947+ mock_validation .is_valid = True
948+ mock_validation .package = APMPackage (name = 'my-skill' , version = '1.0.0' )
949+ mock_validate .return_value = mock_validation
950+
951+ with patch .dict (os .environ , {}, clear = True ):
952+ downloader = GitHubPackageDownloader ()
953+
954+ with patch .object (downloader , '_clone_with_fallback' ) as mock_clone :
955+ mock_clone .return_value = mock_repo
956+
957+ target = self .temp_dir / 'my-skill'
958+
959+ def setup_subdir (* args , ** kwargs ):
960+ clone_path = args [1 ]
961+ subdir = clone_path / 'packages' / 'my-skill'
962+ subdir .mkdir (parents = True )
963+ (subdir / 'apm.yml' ).write_text ('name: my-skill\n version: 1.0.0\n ' )
964+ return mock_repo
965+
966+ mock_clone .side_effect = setup_subdir
967+
968+ downloader .download_subdirectory_package (dep_ref , target )
969+
970+ call_kwargs = mock_clone .call_args
971+ assert call_kwargs .kwargs .get ('depth' ) == 1 , "No ref should still shallow clone"
972+ assert 'branch' not in call_kwargs .kwargs , "No ref should not pass branch"
973+
974+ @patch ('apm_cli.deps.github_downloader.Repo' )
975+ def test_sha_checkout_failure_raises_descriptive_error (self , mock_repo_class ):
976+ """Checkout failure for SHA ref should raise error mentioning 'checkout', not 'clone'."""
977+ sha = 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2'
978+ dep_ref = self ._make_dep_ref (ref = sha )
979+
980+ mock_repo = Mock ()
981+ mock_repo .git .checkout .side_effect = Exception ("bad object a1b2c3d" )
982+ mock_repo_class .return_value = mock_repo
983+
984+ with patch .dict (os .environ , {}, clear = True ):
985+ downloader = GitHubPackageDownloader ()
986+
987+ with patch .object (downloader , '_clone_with_fallback' ) as mock_clone :
988+ def setup_subdir (* args , ** kwargs ):
989+ clone_path = args [1 ]
990+ subdir = clone_path / 'packages' / 'my-skill'
991+ subdir .mkdir (parents = True )
992+ return mock_repo
993+
994+ mock_clone .side_effect = setup_subdir
995+
996+ target = self .temp_dir / 'my-skill'
997+ with pytest .raises (RuntimeError , match = "Failed to checkout commit" ):
998+ downloader .download_subdirectory_package (dep_ref , target )
999+
1000+
8311001if __name__ == '__main__' :
8321002 pytest .main ([__file__ ])
0 commit comments