Skip to content

Commit 8a3f891

Browse files
mushenL杨堃
andauthored
fix(agent_hub): verify visibility on incremental upload to existing repo (#945)
Co-authored-by: 杨堃 <yk01645326@alibaba-inc.com>
1 parent 7b4c13a commit 8a3f891

2 files changed

Lines changed: 68 additions & 0 deletions

File tree

ms_agent/agent_hub/_sync.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,8 @@ def push_mirror(
555555
visibility=visibility)
556556
return
557557

558+
_verify_visibility_or_abort(client, username, name, visibility)
559+
558560
remote_sha_map = {f.path: f.sha256 for f in remote_files}
559561
remote_paths = set(remote_sha_map.keys())
560562
remote_lfs_paths = {f.path for f in remote_files if f.is_lfs}

tests/agent_hub/test_cli.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)