chore: auto release commit (2026-07-03 12:09:55)
All checks were successful
技能自动化发布 / release (push) Successful in 5s
All checks were successful
技能自动化发布 / release (push) Successful in 5s
This commit is contained in:
@@ -1,20 +1,40 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 样例:复制为 test_simulator_rpa_contract.py 后按需运行;默认跳过。
|
||||
"""仿真页面 / RPA 契约(不默认启动浏览器)。"""
|
||||
"""
|
||||
Amazon 仿真 Reports RPA 联调契约样例(不进入默认 unittest 套件)。
|
||||
|
||||
用法(需账号 + 浏览器 + OPENCLAW_TEST_TARGET=simulator_rpa):
|
||||
copy tests\\integration\\test_simulator_rpa_contract.py.sample \\
|
||||
tests\\integration\\test_simulator_rpa_contract.py
|
||||
set OPENCLAW_TEST_TARGET=simulator_rpa
|
||||
python tests\\integration\\test_simulator_rpa_contract.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from adapter_test_utils import should_skip_profile
|
||||
_TESTS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
_SKILL_ROOT = os.path.abspath(os.path.join(_TESTS_DIR, "..", ".."))
|
||||
_SCRIPTS = os.path.join(_SKILL_ROOT, "scripts")
|
||||
for _p in (_SCRIPTS, _TESTS_DIR):
|
||||
if _p not in sys.path:
|
||||
sys.path.insert(0, _p)
|
||||
|
||||
from jiangchang_skill_core import config # noqa: E402
|
||||
|
||||
from service.task_service import cmd_run # noqa: E402
|
||||
|
||||
|
||||
class TestSimulatorRpaContractSample(unittest.TestCase):
|
||||
def test_skip_unless_simulator_rpa_target(self) -> None:
|
||||
skip, reason = should_skip_profile("simulator_rpa")
|
||||
if skip:
|
||||
raise unittest.SkipTest(reason)
|
||||
# 真实技能中:可替换为 Playwright 连接 http://localhost:5180/select 等仿真入口。
|
||||
self.assertFalse(skip)
|
||||
@unittest.skipUnless(
|
||||
os.environ.get("OPENCLAW_TEST_TARGET") == "simulator_rpa",
|
||||
"requires OPENCLAW_TEST_TARGET=simulator_rpa",
|
||||
)
|
||||
class TestSimulatorRpaContract(unittest.TestCase):
|
||||
def test_run_downloads_settlement_xlsx(self) -> None:
|
||||
config.reset_cache()
|
||||
rc = cmd_run(date_from="2025-12-01", date_to="2025-12-31")
|
||||
self.assertEqual(rc, 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
82
tests/test_adapter_dispatch.py
Normal file
82
tests/test_adapter_dispatch.py
Normal file
@@ -0,0 +1,82 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""adapter 工厂 dispatch 单元测试。"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from service.amazon_report_adapter import (
|
||||
DownloadSettlementResult,
|
||||
MockDownloadSettlementAdapter,
|
||||
SimulatorRpaDownloadSettlementAdapter,
|
||||
get_adapter,
|
||||
)
|
||||
|
||||
|
||||
class TestAdapterDispatch(unittest.TestCase):
|
||||
def test_default_returns_simulator_rpa(self) -> None:
|
||||
with patch.dict(os.environ, {}, clear=False):
|
||||
os.environ.pop("OPENCLAW_TEST_TARGET", None)
|
||||
from jiangchang_skill_core import config
|
||||
|
||||
config.reset_cache()
|
||||
adapter = get_adapter()
|
||||
self.assertIsInstance(adapter, SimulatorRpaDownloadSettlementAdapter)
|
||||
|
||||
def test_unit_returns_mock(self) -> None:
|
||||
with patch.dict(os.environ, {"OPENCLAW_TEST_TARGET": "unit"}):
|
||||
from jiangchang_skill_core import config
|
||||
|
||||
config.reset_cache()
|
||||
self.assertIsInstance(get_adapter(), MockDownloadSettlementAdapter)
|
||||
|
||||
def test_mock_returns_mock(self) -> None:
|
||||
with patch.dict(os.environ, {"OPENCLAW_TEST_TARGET": "mock"}):
|
||||
from jiangchang_skill_core import config
|
||||
|
||||
config.reset_cache()
|
||||
self.assertIsInstance(get_adapter(), MockDownloadSettlementAdapter)
|
||||
|
||||
def test_simulator_rpa_returns_simulator(self) -> None:
|
||||
with patch.dict(os.environ, {"OPENCLAW_TEST_TARGET": "simulator_rpa"}):
|
||||
from jiangchang_skill_core import config
|
||||
|
||||
config.reset_cache()
|
||||
self.assertIsInstance(get_adapter(), SimulatorRpaDownloadSettlementAdapter)
|
||||
|
||||
def test_real_rpa_falls_back_to_mock(self) -> None:
|
||||
with patch.dict(os.environ, {"OPENCLAW_TEST_TARGET": "real_rpa"}):
|
||||
from jiangchang_skill_core import config
|
||||
|
||||
config.reset_cache()
|
||||
self.assertIsInstance(get_adapter(), MockDownloadSettlementAdapter)
|
||||
|
||||
def test_unknown_target_falls_back_simulator_rpa(self) -> None:
|
||||
with patch.dict(os.environ, {"OPENCLAW_TEST_TARGET": "simulator_api"}):
|
||||
from jiangchang_skill_core import config
|
||||
|
||||
config.reset_cache()
|
||||
self.assertIsInstance(get_adapter(), SimulatorRpaDownloadSettlementAdapter)
|
||||
|
||||
|
||||
class TestMockAdapter(unittest.TestCase):
|
||||
def test_mock_success(self) -> None:
|
||||
import tempfile
|
||||
|
||||
downloads = tempfile.mkdtemp()
|
||||
result = MockDownloadSettlementAdapter().download_settlement(
|
||||
date_from="2025-12-01",
|
||||
date_to="2025-12-31",
|
||||
seller_code="AMA1OF6T",
|
||||
downloads_dir=downloads,
|
||||
)
|
||||
self.assertTrue(result.ok)
|
||||
self.assertTrue((result.job_id or "").startswith("MOCK-"))
|
||||
self.assertTrue(result.file_path and os.path.isfile(result.file_path))
|
||||
self.assertTrue((result.filename or "").endswith(".xlsx"))
|
||||
self.assertIsInstance(result, DownloadSettlementResult)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -25,7 +25,7 @@ class TestCliSmoke(unittest.TestCase):
|
||||
rc = main([])
|
||||
self.assertEqual(rc, 1)
|
||||
out = buf.getvalue()
|
||||
self.assertIn("通用业务技能模板", out)
|
||||
self.assertIn("Amazon 结算报表", out)
|
||||
self.assertIn("health", out)
|
||||
|
||||
def test_health_zero(self) -> None:
|
||||
@@ -72,19 +72,32 @@ class TestCliSmoke(unittest.TestCase):
|
||||
self.assertNotEqual(rc, 0)
|
||||
self.assertIn("数字", buf.getvalue())
|
||||
|
||||
def test_run_placeholder_returns_nonzero_without_network(self) -> None:
|
||||
"""占位 run:RpaVideoSession 示范 + 模板提示;OPENCLAW_RECORD_VIDEO=0 时不启 ffmpeg。"""
|
||||
def test_run_mock_returns_ok_json(self) -> None:
|
||||
"""mock 档位 run:返回 ok JSON,退出码 0。"""
|
||||
old_auth = os.environ.pop("JIANGCHANG_AUTH_BASE_URL", None)
|
||||
old_record = os.environ.get("OPENCLAW_RECORD_VIDEO")
|
||||
old_target = os.environ.get("OPENCLAW_TEST_TARGET")
|
||||
os.environ["OPENCLAW_RECORD_VIDEO"] = "0"
|
||||
os.environ["OPENCLAW_TEST_TARGET"] = "mock"
|
||||
try:
|
||||
with IsolatedDataRoot(user_id="_cli_run"):
|
||||
config.reset_cache()
|
||||
buf = io.StringIO()
|
||||
with redirect_stdout(buf), redirect_stderr(io.StringIO()):
|
||||
rc = main(["run"])
|
||||
self.assertNotEqual(rc, 0)
|
||||
self.assertIn("模板", buf.getvalue())
|
||||
rc = main(
|
||||
[
|
||||
"run",
|
||||
"--date-from",
|
||||
"2025-12-01",
|
||||
"--date-to",
|
||||
"2025-12-31",
|
||||
]
|
||||
)
|
||||
self.assertEqual(rc, 0)
|
||||
payload = json.loads(buf.getvalue().strip().splitlines()[-1])
|
||||
self.assertTrue(payload.get("ok"))
|
||||
self.assertIn("job_id", payload)
|
||||
self.assertIn("file_path", payload)
|
||||
finally:
|
||||
if old_auth is not None:
|
||||
os.environ["JIANGCHANG_AUTH_BASE_URL"] = old_auth
|
||||
@@ -92,6 +105,11 @@ class TestCliSmoke(unittest.TestCase):
|
||||
os.environ.pop("OPENCLAW_RECORD_VIDEO", None)
|
||||
else:
|
||||
os.environ["OPENCLAW_RECORD_VIDEO"] = old_record
|
||||
if old_target is None:
|
||||
os.environ.pop("OPENCLAW_TEST_TARGET", None)
|
||||
else:
|
||||
os.environ["OPENCLAW_TEST_TARGET"] = old_target
|
||||
config.reset_cache()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""RpaVideoSession 模板示范与 result_summary / CLI 输出测试。"""
|
||||
"""RpaVideoSession 与 cmd_run 测试。"""
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
@@ -26,6 +26,7 @@ class TestEnvExampleVideoDefaults(unittest.TestCase):
|
||||
"OPENCLAW_ARTIFACTS_ON_FAILURE=1",
|
||||
"OPENCLAW_BROWSER_HEADLESS=0",
|
||||
"OPENCLAW_PLAYWRIGHT_STEALTH=1",
|
||||
"AMAZON_SIM_REPORTS_URL=https://sandbox.jc2009.com",
|
||||
):
|
||||
self.assertIn(key, text, msg=f"missing {key!r} in .env.example")
|
||||
|
||||
@@ -39,70 +40,35 @@ class TestPrintVideoSummary(unittest.TestCase):
|
||||
_print_video_summary(
|
||||
{
|
||||
"enabled": True,
|
||||
"path": r"D:\data\videos\your-skill-slug_demo.mp4",
|
||||
"path": r"D:\data\videos\download-settlement-report-amazon_demo.mp4",
|
||||
"record_log_path": r"D:\data\logs\ffmpeg-record.log",
|
||||
"warnings": [],
|
||||
}
|
||||
)
|
||||
out = buf.getvalue()
|
||||
self.assertIn("录屏路径", out)
|
||||
self.assertIn("your-skill-slug_demo.mp4", out)
|
||||
self.assertIn("download-settlement-report-amazon_demo.mp4", out)
|
||||
self.assertIn("录屏日志", out)
|
||||
|
||||
def test_disabled_video_prints_nothing(self) -> None:
|
||||
from service.task_run_support import _print_video_summary
|
||||
|
||||
buf = io.StringIO()
|
||||
with redirect_stdout(buf), redirect_stderr(io.StringIO()):
|
||||
_print_video_summary({"enabled": False, "path": r"D:\tmp\x.mp4"})
|
||||
self.assertEqual(buf.getvalue(), "")
|
||||
|
||||
|
||||
class TestMergeVideoIntoResultSummary(unittest.TestCase):
|
||||
def test_result_summary_includes_video_fields(self) -> None:
|
||||
from service.task_run_support import merge_video_into_result_summary
|
||||
|
||||
video_info = {
|
||||
"enabled": True,
|
||||
"path": r"D:\data\videos\demo.mp4",
|
||||
"capture_path": r"D:\data\capture.mp4",
|
||||
"record_log_path": r"D:\data\logs\ffmpeg-record.log",
|
||||
"warnings": ["test_warning"],
|
||||
"voiceover_path": r"D:\data\voiceover.wav",
|
||||
"music_path": r"D:\data\music.mp3",
|
||||
"audio_warnings": ["tts_skipped"],
|
||||
}
|
||||
payload = merge_video_into_result_summary({"demo": True}, video_info)
|
||||
self.assertIn("video", payload)
|
||||
self.assertEqual(payload["video_path"], video_info["path"])
|
||||
self.assertEqual(payload["raw_video"], video_info["capture_path"])
|
||||
self.assertEqual(payload["video_log"], video_info["record_log_path"])
|
||||
self.assertEqual(payload["video_warnings"], ["test_warning"])
|
||||
self.assertEqual(payload["voiceover_path"], video_info["voiceover_path"])
|
||||
self.assertEqual(payload["music_path"], video_info["music_path"])
|
||||
self.assertEqual(payload["audio_warnings"], ["tts_skipped"])
|
||||
|
||||
|
||||
class TestTemplateRunVideoSession(unittest.TestCase):
|
||||
def _fake_video_session(self, mock_video: MagicMock):
|
||||
class _FakeVideoSession:
|
||||
def __init__(self, **kwargs):
|
||||
self.kwargs = kwargs
|
||||
|
||||
async def __aenter__(self):
|
||||
return mock_video
|
||||
|
||||
async def __aexit__(self, *args):
|
||||
return None
|
||||
|
||||
return _FakeVideoSession
|
||||
|
||||
class TestCmdRunVideoSession(unittest.TestCase):
|
||||
def test_cmd_run_creates_video_session_with_chinese_titles(self) -> None:
|
||||
with IsolatedDataRoot(user_id="_video_run"):
|
||||
os.environ["OPENCLAW_RECORD_VIDEO"] = "0"
|
||||
os.environ["OPENCLAW_RECORD_VIDEO"] = "1"
|
||||
os.environ["OPENCLAW_TEST_TARGET"] = "mock"
|
||||
config.reset_cache()
|
||||
|
||||
from service import task_service
|
||||
from service.amazon_report_adapter.base import DownloadSettlementResult
|
||||
|
||||
mock_result = DownloadSettlementResult(
|
||||
ok=True,
|
||||
job_id="MOCK-1",
|
||||
file_path="/tmp/settlement.xlsx",
|
||||
filename="settlement.xlsx",
|
||||
date_from="2025-12-01",
|
||||
date_to="2025-12-31",
|
||||
)
|
||||
|
||||
mock_video = MagicMock()
|
||||
mock_video.add_step = MagicMock()
|
||||
@@ -129,32 +95,35 @@ class TestTemplateRunVideoSession(unittest.TestCase):
|
||||
async def __aexit__(self, *args):
|
||||
return None
|
||||
|
||||
with patch.object(task_service, "RpaVideoSession", _CapturingFakeSession):
|
||||
buf = io.StringIO()
|
||||
with redirect_stdout(buf), redirect_stderr(io.StringIO()):
|
||||
rc = task_service.cmd_run(target="demo-target", input_id="42")
|
||||
mock_adapter = MagicMock()
|
||||
mock_adapter.name = "mock"
|
||||
mock_adapter.download_settlement.return_value = mock_result
|
||||
|
||||
self.assertEqual(rc, 1)
|
||||
with patch.object(task_service, "RpaVideoSession", _CapturingFakeSession):
|
||||
with patch.object(task_service, "get_adapter", return_value=mock_adapter):
|
||||
buf = io.StringIO()
|
||||
with redirect_stdout(buf), redirect_stderr(io.StringIO()):
|
||||
rc = task_service.cmd_run(
|
||||
date_from="2025-12-01",
|
||||
date_to="2025-12-31",
|
||||
)
|
||||
|
||||
self.assertEqual(rc, 0)
|
||||
self.assertTrue(created, "RpaVideoSession should be constructed")
|
||||
kwargs = created[0]
|
||||
self.assertEqual(kwargs.get("title"), "开始执行示例任务")
|
||||
self.assertEqual(kwargs.get("closing_title"), "示例任务执行完成")
|
||||
self.assertEqual(kwargs.get("title"), "开始下载 Amazon 结算报表")
|
||||
self.assertEqual(kwargs.get("closing_title"), "结算报表下载完成")
|
||||
self.assertTrue(re.search(r"[\u4e00-\u9fff]", kwargs.get("title", "")))
|
||||
self.assertTrue(re.search(r"[\u4e00-\u9fff]", kwargs.get("closing_title", "")))
|
||||
mock_video.add_step.assert_any_call("准备执行示例任务")
|
||||
mock_video.add_step.assert_any_call("示例任务执行完成")
|
||||
mock_video.summary.assert_called()
|
||||
|
||||
out = buf.getvalue()
|
||||
self.assertIn("模板", out)
|
||||
payload = json.loads(out.strip().splitlines()[-1])
|
||||
self.assertTrue(payload.get("ok"))
|
||||
|
||||
from db import task_logs_repository as tlr
|
||||
|
||||
rows = tlr.list_task_logs(1)
|
||||
self.assertTrue(rows)
|
||||
summary = json.loads(rows[0][7])
|
||||
self.assertIn("video", summary)
|
||||
self.assertIn("video_path", summary)
|
||||
self.assertEqual(rows[0][1], "download_settlement")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user