feat: 强化打包配置与服务安全

This commit is contained in:
shen
2026-07-18 13:30:56 +08:00
parent d1f070b251
commit a48909f3bc
26 changed files with 779 additions and 203 deletions
+42 -7
View File
@@ -13,6 +13,8 @@ import pytest
from backend.services.build_service import (
update_source,
copy_source_code,
prepare_source_snapshot,
build_project,
generate_config,
_cleanup_old_builds,
)
@@ -69,10 +71,8 @@ async def test_update_source_clone(tmp_path, log_streamer):
with patch("backend.services.build_service.get_git_remote_url", return_value="git@github.com:test/repo.git"):
with patch("asyncio.create_subprocess_exec", return_value=_make_mock_process()) as mock_exec:
await update_source("t1", source_dir, "main")
mock_exec.assert_called_once()
args = mock_exec.call_args[0]
assert "git" in args
assert "clone" in args
assert mock_exec.call_count == 6 # clone, set-url, fetch, checkout, reset, clean
assert mock_exec.call_args_list[0].args[:2] == ("git", "clone")
async def test_update_source_clone_no_remote(tmp_path, log_streamer):
@@ -84,8 +84,8 @@ async def test_update_source_clone_no_remote(tmp_path, log_streamer):
await update_source("t1", source_dir, "main")
async def test_update_source_pull_existing(tmp_path, log_streamer):
"""目录已存在时执行 fetch + checkout + pull"""
async def test_update_source_existing(tmp_path, log_streamer):
"""目录已存在时执行 fetch + checkout + reset + clean"""
source_dir = tmp_path / "branches" / "dev"
source_dir.parent.mkdir(parents=True)
source_dir.mkdir()
@@ -102,7 +102,7 @@ async def test_update_source_pull_existing(tmp_path, log_streamer):
with patch("asyncio.create_subprocess_exec", side_effect=mock_exec):
await update_source("t1", source_dir, "dev")
assert call_count == 3 # fetch, checkout, pull
assert call_count == 4 # fetch, checkout, reset, clean
async def test_update_source_fetch_failure(tmp_path, log_streamer):
@@ -110,6 +110,7 @@ async def test_update_source_fetch_failure(tmp_path, log_streamer):
source_dir = tmp_path / "branches" / "dev"
source_dir.parent.mkdir(parents=True)
source_dir.mkdir()
(source_dir / ".git").mkdir()
async def mock_exec(*args, **kwargs):
return _make_mock_process(returncode=1, output=b"error\n")
@@ -120,6 +121,22 @@ async def test_update_source_fetch_failure(tmp_path, log_streamer):
await update_source("t1", source_dir, "dev")
async def test_prepare_source_snapshot_serializes_shared_source(tmp_dirs, log_streamer):
"""共享源码模式下,更新和复制必须在同一把锁内完成。"""
source_dir, build_dir_parent = tmp_dirs
task = MagicMock(branch="main")
with patch("backend.services.build_service.get_git_remote_url", return_value="git@github.com:test/repo.git"), \
patch("backend.services.build_service.get_shared_source_dir", return_value=source_dir), \
patch("backend.services.build_service.BUILD_BASE_DIR", build_dir_parent), \
patch("backend.services.build_service.update_source", new_callable=AsyncMock), \
patch("backend.services.build_service.get_source_commit", new_callable=AsyncMock, return_value="abc123"):
build_dir, commit = await prepare_source_snapshot("t1", task)
assert build_dir.exists()
assert commit == "abc123"
# ---- copy_source_code ----
async def test_copy_source_code(tmp_dirs, log_streamer):
@@ -170,6 +187,24 @@ async def test_copy_source_code_overwrites_existing(tmp_dirs, log_streamer):
assert (result / "readoor").exists()
async def test_build_project_passes_scheme_as_exec_argument(tmp_path, log_streamer):
"""Scheme 中的 shell 特殊字符只能作为 xcodebuild 参数,不能被执行。"""
build_dir = tmp_path / "build"
build_dir.mkdir()
task = MagicMock()
config_data = {"SCHEME": "App; touch /tmp/should-not-run"}
with patch("asyncio.create_subprocess_exec", return_value=_make_mock_process()) as mock_exec, \
patch("asyncio.create_subprocess_shell") as mock_shell:
with pytest.raises(Exception, match="未找到 IPA 文件"):
await build_project("t1", task, config_data, build_dir)
assert mock_shell.call_count == 0
archive_args = mock_exec.call_args_list[1].args
assert archive_args[0:2] == ("xcodebuild", "archive")
assert "App; touch /tmp/should-not-run" in archive_args
# ---- generate_config ----
async def test_generate_config(tmp_path, log_streamer):