@@ -257,6 +257,9 @@ class _StubClient:
257257 # simulate the server ignoring a private request.
258258 preset_repo_info = {}
259259
260+ # Per-path remote sha256 from list_repo_files_detail, to simulate incremental/up-to-date remotes.
261+ preset_remote_sha = {}
262+
260263 def __init__ (self , * args , ** kwargs ):
261264 self .created = []
262265 self .created_visibility = []
@@ -295,6 +298,17 @@ def commit_files(self, path, name, actions, **kwargs):
295298 self .uploaded_resources [a ["path" ]] = base64 .b64decode (a ["content" ])
296299 return {"success" : True }
297300
301+ def list_repo_files_detail (self , path , name , revision = "master" ):
302+ from ms_agent .agent_hub ._sync import sha256_content
303+ shas = type (self ).preset_remote_sha
304+ return [
305+ RemoteFileInfo (
306+ path = p ,
307+ sha256 = shas .get (p , sha256_content (("stub:" + p ).encode ())),
308+ is_lfs = False ,
309+ ) for p in type (self ).preset_remote
310+ ]
311+
298312 def upload_lfs_file (self , path , name , file_path , content , ** kwargs ):
299313 self .lfs_uploads .append ((file_path , content ))
300314 if self .uploaded_resources is None :
@@ -313,6 +327,9 @@ def setUp(self):
313327 (self .root / "skills" / "test-skill" ).mkdir (parents = True )
314328 (self .root / "skills" / "test-skill" / "SKILL.md" ).write_text ("skill" )
315329 _StubClient .instances = []
330+ _StubClient .preset_remote = []
331+ _StubClient .preset_repo_info = {}
332+ _StubClient .preset_remote_sha = {}
316333
317334 def tearDown (self ):
318335 self .tmp .cleanup ()
@@ -411,6 +428,55 @@ def test_unverifiable_visibility_proceeds(self):
411428 self .assertEqual (rc , 0 )
412429 self .assertIsNotNone (_StubClient .instances [0 ].uploaded_resources )
413430
431+ @mock .patch ("ms_agent.agent_hub._commands.AgentApi" , _StubClient )
432+ def test_private_request_existing_public_repo_aborts_before_upload (self ):
433+ """Existing public repo + private request -> abort before any upload."""
434+ _StubClient .preset_repo_info = {"private" : False }
435+ _StubClient .preset_remote = ["AGENTS.md" , "agents/reviewer.md" ]
436+ try :
437+ rc = cmd_upload (
438+ framework = "qoder" , name = "reviewer" ,
439+ local_dir = str (self .root ),
440+ endpoint = "http://s" , token = "tok" , username = "u" ,
441+ visibility = "private" ,
442+ )
443+ finally :
444+ _StubClient .preset_repo_info = {}
445+ _StubClient .preset_remote = []
446+ _StubClient .preset_remote_sha = {}
447+ self .assertEqual (rc , 1 ) # non-zero: the failure is observable on CLI
448+ client = _StubClient .instances [0 ]
449+ # No content was pushed after the visibility mismatch.
450+ self .assertIsNone (client .uploaded_resources )
451+
452+ @mock .patch ("ms_agent.agent_hub._commands.AgentApi" , _StubClient )
453+ def test_private_request_existing_public_repo_aborts_even_when_up_to_date (
454+ self ):
455+ """In-sync remote still aborts: guard runs before the up-to-date early return."""
456+ from ms_agent .agent_hub ._sync import sha256_content
457+ _StubClient .preset_repo_info = {"private" : False }
458+ _StubClient .preset_remote = ["AGENTS.md" , "agents/reviewer.md" ]
459+ _StubClient .preset_remote_sha = {
460+ "AGENTS.md" :
461+ sha256_content ((self .root / "AGENTS.md" ).read_bytes ()),
462+ "agents/reviewer.md" :
463+ sha256_content ((self .root / "agents" / "reviewer.md" ).read_bytes ()),
464+ }
465+ try :
466+ rc = cmd_upload (
467+ framework = "qoder" , name = "reviewer" ,
468+ local_dir = str (self .root ),
469+ endpoint = "http://s" , token = "tok" , username = "u" ,
470+ visibility = "private" ,
471+ )
472+ finally :
473+ _StubClient .preset_repo_info = {}
474+ _StubClient .preset_remote = []
475+ _StubClient .preset_remote_sha = {}
476+ self .assertEqual (rc , 1 ) # non-zero: the failure is observable on CLI
477+ # No content was pushed despite the remote already being in sync.
478+ self .assertIsNone (_StubClient .instances [0 ].uploaded_resources )
479+
414480 @mock .patch ("ms_agent.agent_hub._commands.AgentApi" , _StubClient )
415481 def test_full_upload_prunes_stale_remote_in_scope (self ):
416482 """Mirror semantics: remote files in scope but not local are deleted."""
0 commit comments