feat: expose win helper via mcp real lab
This commit is contained in:
90
tests/test_draft_queue.py
Normal file
90
tests/test_draft_queue.py
Normal file
@@ -0,0 +1,90 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import tempfile
|
||||
import unittest
|
||||
from contextlib import redirect_stdout
|
||||
from io import StringIO
|
||||
from pathlib import Path
|
||||
|
||||
from isphere_ai_bridge.cli import main
|
||||
from isphere_ai_bridge.draft_queue import DraftQueue
|
||||
from isphere_ai_bridge.models import Draft
|
||||
from isphere_ai_bridge.mock_db import create_mock_msglib
|
||||
|
||||
|
||||
class DraftQueueTests(unittest.TestCase):
|
||||
def test_append_drafts_writes_pending_queue_and_redacted_audit_event(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
queue_path = Path(tmp) / "drafts.jsonl"
|
||||
audit_path = Path(tmp) / "audit.jsonl"
|
||||
queue = DraftQueue(
|
||||
queue_path=queue_path,
|
||||
audit_path=audit_path,
|
||||
clock=lambda: "2026-07-05T01:40:00+08:00",
|
||||
)
|
||||
draft = Draft(
|
||||
message_id="msg-001",
|
||||
conversation_id="alice@example",
|
||||
draft_text="Draft only. Please review before sending.",
|
||||
source_message_ref="MsgLib.db#tblPersonMsg:msg-001",
|
||||
)
|
||||
|
||||
entries = queue.append_drafts([draft])
|
||||
|
||||
self.assertEqual(1, len(entries))
|
||||
self.assertEqual("pending_review", entries[0]["status"])
|
||||
self.assertEqual("msg-001", entries[0]["message_id"])
|
||||
self.assertEqual("Draft only. Please review before sending.", entries[0]["draft_text"])
|
||||
|
||||
queue_lines = queue_path.read_text(encoding="utf-8").splitlines()
|
||||
self.assertEqual(1, len(queue_lines))
|
||||
queue_record = json.loads(queue_lines[0])
|
||||
self.assertEqual(entries[0]["draft_id"], queue_record["draft_id"])
|
||||
self.assertEqual("pending_review", queue_record["status"])
|
||||
|
||||
audit_lines = audit_path.read_text(encoding="utf-8").splitlines()
|
||||
self.assertEqual(1, len(audit_lines))
|
||||
audit_record = json.loads(audit_lines[0])
|
||||
self.assertEqual("draft_created", audit_record["event_type"])
|
||||
self.assertEqual(entries[0]["draft_id"], audit_record["draft_id"])
|
||||
self.assertIn("content_sha256", audit_record)
|
||||
self.assertNotIn("draft_text", audit_record)
|
||||
|
||||
def test_queue_drafts_cli_appends_drafts_and_audit_events(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
db_path = create_mock_msglib(root / "MsgLib.db")
|
||||
queue_path = root / "drafts.jsonl"
|
||||
audit_path = root / "audit.jsonl"
|
||||
stdout = StringIO()
|
||||
|
||||
with redirect_stdout(stdout):
|
||||
exit_code = main(
|
||||
[
|
||||
"queue-drafts",
|
||||
"--db",
|
||||
str(db_path),
|
||||
"--queue",
|
||||
str(queue_path),
|
||||
"--audit",
|
||||
str(audit_path),
|
||||
"--limit",
|
||||
"2",
|
||||
]
|
||||
)
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
output = json.loads(stdout.getvalue())
|
||||
self.assertEqual(2, output["queued"])
|
||||
self.assertEqual(2, len(queue_path.read_text(encoding="utf-8").splitlines()))
|
||||
audit_records = [
|
||||
json.loads(line)
|
||||
for line in audit_path.read_text(encoding="utf-8").splitlines()
|
||||
]
|
||||
self.assertEqual(2, len(audit_records))
|
||||
self.assertNotIn("draft_text", audit_records[0])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
73
tests/test_local_db_poc.py
Normal file
73
tests/test_local_db_poc.py
Normal file
@@ -0,0 +1,73 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlite3
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from isphere_ai_bridge.draft import generate_drafts
|
||||
from isphere_ai_bridge.local_db import ISphereLocalDbReader, open_readonly_connection
|
||||
from isphere_ai_bridge.locator import discover_msglib_databases
|
||||
from isphere_ai_bridge.mock_db import create_mock_msglib
|
||||
|
||||
|
||||
class LocalDbPocTests(unittest.TestCase):
|
||||
def test_mock_database_reads_normalized_messages(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
db_path = create_mock_msglib(Path(tmp) / "MsgLib.db")
|
||||
reader = ISphereLocalDbReader(db_path)
|
||||
|
||||
probe = reader.probe()
|
||||
self.assertTrue(probe["can_open_readonly"])
|
||||
self.assertIn("tblPersonMsg", probe["message_tables"])
|
||||
self.assertIn("tblMsgGroupPersonMsg", probe["message_tables"])
|
||||
|
||||
messages = reader.read_messages(limit=10)
|
||||
self.assertEqual(4, len(messages))
|
||||
self.assertEqual("sys-001", messages[0].message_id)
|
||||
self.assertEqual("system", messages[0].conversation_type)
|
||||
self.assertEqual("grp-001", messages[1].message_id)
|
||||
self.assertEqual("group", messages[1].conversation_type)
|
||||
self.assertEqual("p2p-002", messages[2].message_id)
|
||||
self.assertEqual("direct", messages[2].conversation_type)
|
||||
self.assertIn("#tblPersonMsg:p2p-002", messages[2].raw_ref)
|
||||
|
||||
def test_connection_is_read_only(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
db_path = create_mock_msglib(Path(tmp) / "MsgLib.db")
|
||||
with open_readonly_connection(db_path) as con:
|
||||
with self.assertRaises(sqlite3.OperationalError):
|
||||
con.execute("create table should_not_write (id int)")
|
||||
|
||||
def test_discovers_expected_account_msglib_path(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
base = Path(tmp) / "isphere"
|
||||
db_path = (
|
||||
base
|
||||
/ "connection.10083"
|
||||
/ "27000000"
|
||||
/ "Account"
|
||||
/ "EMP001"
|
||||
/ "MsgLib.db"
|
||||
)
|
||||
create_mock_msglib(db_path)
|
||||
|
||||
candidates = discover_msglib_databases(base)
|
||||
self.assertEqual(1, len(candidates))
|
||||
self.assertTrue(candidates[0].exists)
|
||||
self.assertEqual("EMP001", candidates[0].account_id)
|
||||
self.assertEqual(db_path, candidates[0].path)
|
||||
|
||||
def test_generates_draft_only_outputs(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
db_path = create_mock_msglib(Path(tmp) / "MsgLib.db")
|
||||
messages = ISphereLocalDbReader(db_path).read_messages(limit=2)
|
||||
drafts = generate_drafts(messages)
|
||||
|
||||
self.assertEqual(2, len(drafts))
|
||||
self.assertEqual("draft-only", drafts[0].status)
|
||||
self.assertIn("Draft only", drafts[0].draft_text)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
51
tests/test_mcp_api.py
Normal file
51
tests/test_mcp_api.py
Normal file
@@ -0,0 +1,51 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from isphere_ai_bridge import mcp_api
|
||||
|
||||
|
||||
class MCPApiTests(unittest.TestCase):
|
||||
def test_mcp_api_reads_mock_messages_and_queues_drafts(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
db_path = root / "MsgLib.db"
|
||||
queue_path = root / "drafts.jsonl"
|
||||
audit_path = root / "audit.jsonl"
|
||||
|
||||
created = mcp_api.create_mock_source(str(db_path))
|
||||
self.assertEqual(str(db_path), created["created"])
|
||||
|
||||
messages = mcp_api.read_messages(str(db_path), limit=2)
|
||||
self.assertEqual(2, messages["count"])
|
||||
self.assertEqual("local-db", messages["messages"][0]["source"])
|
||||
|
||||
queued = mcp_api.queue_drafts_for_review(
|
||||
str(db_path),
|
||||
queue_path=str(queue_path),
|
||||
audit_path=str(audit_path),
|
||||
limit=2,
|
||||
)
|
||||
self.assertEqual(2, queued["queued"])
|
||||
self.assertEqual("pending_review", queued["status"])
|
||||
|
||||
audit = mcp_api.read_audit_events(str(audit_path), limit=5)
|
||||
self.assertEqual(2, audit["count"])
|
||||
self.assertIn("content_sha256", audit["events"][0])
|
||||
self.assertNotIn("draft_text", audit["events"][0])
|
||||
|
||||
def test_limit_is_bounded_for_mcp_tools(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
db_path = Path(tmp) / "MsgLib.db"
|
||||
mcp_api.create_mock_source(str(db_path))
|
||||
|
||||
messages = mcp_api.read_messages(str(db_path), limit=1000)
|
||||
|
||||
self.assertEqual(mcp_api.MAX_LIMIT, messages["limit"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
159
tests/test_native_helper.py
Normal file
159
tests/test_native_helper.py
Normal file
@@ -0,0 +1,159 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from isphere_ai_bridge import native_helper
|
||||
|
||||
|
||||
def _has_framework_csc() -> bool:
|
||||
windir = Path(os.environ.get("WINDIR", r"C:\Windows"))
|
||||
return (
|
||||
windir / "Microsoft.NET" / "Framework64" / "v4.0.30319" / "csc.exe"
|
||||
).exists() or (
|
||||
windir / "Microsoft.NET" / "Framework" / "v4.0.30319" / "csc.exe"
|
||||
).exists()
|
||||
|
||||
|
||||
class NativeHelperTests(unittest.TestCase):
|
||||
def test_helper_paths_are_actionable(self) -> None:
|
||||
paths = native_helper.helper_paths()
|
||||
|
||||
self.assertIn("build_command", paths)
|
||||
self.assertTrue(paths["build_script_exists"])
|
||||
self.assertTrue(paths["default_client_dir_exists"])
|
||||
|
||||
@unittest.skipUnless(_has_framework_csc(), ".NET Framework csc.exe is required")
|
||||
def test_native_helper_build_inspect_and_offline_protocol(self) -> None:
|
||||
build = native_helper.build_native_helper()
|
||||
self.assertTrue(build["ok"], build)
|
||||
self.assertEqual(0, build["returncode"])
|
||||
self.assertTrue(Path(build["output"]).exists())
|
||||
self.assertTrue(Path(build["resource_stub"]).exists())
|
||||
self.assertEqual("x86", build["platform"])
|
||||
|
||||
inspect = native_helper.native_lab_inspect()
|
||||
self.assertTrue(inspect["ok"], inspect)
|
||||
self.assertTrue(inspect["required_ready"])
|
||||
type_names = {item["name"] for item in inspect["types"]}
|
||||
self.assertIn("IMPP.Client.frmMain", type_names)
|
||||
self.assertIn("IMPP.Client.Business.ChatManager.SingleChat.frmP2PChat", type_names)
|
||||
|
||||
bootstrap = native_helper.native_lab_probe_main_window()
|
||||
self.assertTrue(bootstrap["ok"], bootstrap)
|
||||
bootstrap_steps = {item["name"]: item["ok"] for item in bootstrap["steps"]}
|
||||
self.assertTrue(bootstrap_steps["imppmanager_update_user"])
|
||||
self.assertFalse(bootstrap["main_window_constructed"])
|
||||
|
||||
constructed = native_helper.native_lab_probe_main_window(
|
||||
construct=True,
|
||||
probe_chat=True,
|
||||
probe_actions=True,
|
||||
probe_files=True,
|
||||
user_jid="mock-bob@offline.lab",
|
||||
text="native helper direct test",
|
||||
file_name="incoming-probe-native.txt",
|
||||
file_size=321,
|
||||
timeout_ms=15000,
|
||||
)
|
||||
self.assertTrue(constructed["ok"], constructed)
|
||||
self.assertTrue(constructed["main_window_constructed"])
|
||||
self.assertTrue(constructed["chat_model_constructed"])
|
||||
self.assertTrue(constructed["chat_window_constructed"])
|
||||
self.assertTrue(constructed["send_text_invoked"])
|
||||
self.assertTrue(constructed["receive_text_invoked"])
|
||||
self.assertTrue(constructed["file_send_control_constructed"])
|
||||
self.assertTrue(constructed["file_receive_control_constructed"])
|
||||
construct_steps = {item["name"]: item["ok"] for item in constructed["steps"]}
|
||||
self.assertTrue(construct_steps["patch_problematic_ui_methods"])
|
||||
self.assertTrue(construct_steps["instantiate_frmMain"])
|
||||
self.assertTrue(construct_steps["instantiate_frmP2PChat"])
|
||||
self.assertTrue(construct_steps["invoke_frmP2PChat_SaveAndShowMessage"])
|
||||
self.assertTrue(construct_steps["invoke_frmP2PChat_ProccessRecvMessage"])
|
||||
self.assertTrue(construct_steps["instantiate_UC_FileSend"])
|
||||
self.assertTrue(construct_steps["instantiate_UC_FileReceive"])
|
||||
|
||||
direct_open = native_helper.native_lab_direct_open_main_window(timeout_ms=15000)
|
||||
self.assertTrue(direct_open["ok"], direct_open)
|
||||
self.assertTrue(direct_open["main_window_constructed"])
|
||||
|
||||
direct_send = native_helper.native_lab_direct_send_message(
|
||||
"mock-bob@offline.lab",
|
||||
"direct wrapper send",
|
||||
timeout_ms=15000,
|
||||
)
|
||||
self.assertTrue(direct_send["ok"], direct_send)
|
||||
self.assertTrue(direct_send["send_text_invoked"])
|
||||
self.assertFalse(direct_send["receive_text_invoked"])
|
||||
|
||||
direct_receive = native_helper.native_lab_direct_receive_message(
|
||||
"mock-bob@offline.lab",
|
||||
"direct wrapper receive",
|
||||
timeout_ms=15000,
|
||||
)
|
||||
self.assertTrue(direct_receive["ok"], direct_receive)
|
||||
self.assertFalse(direct_receive["send_text_invoked"])
|
||||
self.assertTrue(direct_receive["receive_text_invoked"])
|
||||
|
||||
direct_send_file = native_helper.native_lab_direct_send_file(
|
||||
"mock-bob@offline.lab",
|
||||
timeout_ms=15000,
|
||||
)
|
||||
self.assertTrue(direct_send_file["ok"], direct_send_file)
|
||||
self.assertTrue(direct_send_file["file_send_control_constructed"])
|
||||
self.assertFalse(direct_send_file["file_receive_control_constructed"])
|
||||
|
||||
direct_receive_file = native_helper.native_lab_direct_receive_file(
|
||||
"mock-bob@offline.lab",
|
||||
"direct-wrapper-incoming.txt",
|
||||
size=222,
|
||||
timeout_ms=15000,
|
||||
)
|
||||
self.assertTrue(direct_receive_file["ok"], direct_receive_file)
|
||||
self.assertFalse(direct_receive_file["file_send_control_constructed"])
|
||||
self.assertTrue(direct_receive_file["file_receive_control_constructed"])
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
workspace = str(Path(tmp) / "native-lab")
|
||||
|
||||
start = native_helper.native_lab_start(workspace=workspace, reset=True)
|
||||
self.assertTrue(start["started"])
|
||||
self.assertEqual("offline-lab-native-helper", start["mode"])
|
||||
self.assertEqual("inspected", start["native_bridge"]["status"])
|
||||
|
||||
users = native_helper.native_lab_search_users("文件", workspace=workspace)
|
||||
self.assertEqual(1, users["count"])
|
||||
user_jid = users["users"][0]["jid"]
|
||||
|
||||
opened = native_helper.native_lab_open_chat(user_jid, workspace=workspace)
|
||||
self.assertTrue(opened["opened"])
|
||||
self.assertEqual("p2p-chat", opened["main_window"]["active_panel"])
|
||||
|
||||
sent = native_helper.native_lab_send_message(
|
||||
user_jid,
|
||||
"native helper test send",
|
||||
workspace=workspace,
|
||||
)
|
||||
self.assertTrue(sent["sent"])
|
||||
self.assertEqual("frmP2PChat.SaveAndShowMessage", sent["message"]["ui_hook"])
|
||||
|
||||
received_file = native_helper.native_lab_receive_file(
|
||||
user_jid,
|
||||
"incoming-native.txt",
|
||||
size=789,
|
||||
workspace=workspace,
|
||||
)
|
||||
self.assertTrue(received_file["received"])
|
||||
self.assertEqual(
|
||||
"frmP2PChat.ProccessRecvMessage -> UC_FileReceive",
|
||||
received_file["file_event"]["ui_hook"],
|
||||
)
|
||||
|
||||
conversation = native_helper.native_lab_read_conversation(user_jid, workspace=workspace)
|
||||
self.assertEqual(2, conversation["conversation"]["message_count"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
91
tests/test_offline_lab.py
Normal file
91
tests/test_offline_lab.py
Normal file
@@ -0,0 +1,91 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from isphere_ai_bridge import mcp_api
|
||||
|
||||
|
||||
class OfflineLabTests(unittest.TestCase):
|
||||
def test_offline_lab_simulates_main_window_search_chat_and_files(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
workspace = str(Path(tmp) / "offline-lab")
|
||||
|
||||
start = mcp_api.offline_lab_start(workspace=workspace, reset=True)
|
||||
self.assertTrue(start["started"])
|
||||
self.assertEqual("offline-lab-simulated", start["mode"])
|
||||
self.assertTrue(start["main_window"]["opened"])
|
||||
|
||||
users = mcp_api.offline_lab_search_users("文件", workspace=workspace)
|
||||
self.assertEqual(1, users["count"])
|
||||
user_jid = users["users"][0]["jid"]
|
||||
|
||||
opened = mcp_api.offline_lab_open_chat(user_jid, workspace=workspace)
|
||||
self.assertTrue(opened["opened"])
|
||||
self.assertEqual("p2p-chat", opened["main_window"]["active_panel"])
|
||||
|
||||
sent = mcp_api.offline_lab_send_message(
|
||||
user_jid,
|
||||
"这是一条离线模拟发送消息",
|
||||
workspace=workspace,
|
||||
)
|
||||
self.assertTrue(sent["sent"])
|
||||
self.assertEqual("outgoing", sent["message"]["direction"])
|
||||
self.assertEqual("frmP2PChat.SaveAndShowMessage", sent["message"]["ui_hook"])
|
||||
|
||||
received = mcp_api.offline_lab_receive_message(
|
||||
user_jid,
|
||||
"这是一条离线模拟接收消息",
|
||||
workspace=workspace,
|
||||
)
|
||||
self.assertTrue(received["received"])
|
||||
self.assertEqual("incoming", received["message"]["direction"])
|
||||
self.assertEqual("frmP2PChat.ProccessRecvMessage", received["message"]["ui_hook"])
|
||||
|
||||
outbound_file = Path(tmp) / "demo.txt"
|
||||
outbound_file.write_text("file payload", encoding="utf-8")
|
||||
send_file = mcp_api.offline_lab_send_file(
|
||||
user_jid,
|
||||
str(outbound_file),
|
||||
workspace=workspace,
|
||||
)
|
||||
self.assertTrue(send_file["sent"])
|
||||
self.assertEqual("file", send_file["file_event"]["content_type"])
|
||||
self.assertEqual(True, send_file["file_event"]["attachments"][0]["exists"])
|
||||
self.assertEqual(
|
||||
"frmP2PChat.SendTcpFiles/SendOfflineFiles -> UC_FileSend",
|
||||
send_file["file_event"]["ui_hook"],
|
||||
)
|
||||
|
||||
receive_file = mcp_api.offline_lab_receive_file(
|
||||
user_jid,
|
||||
"incoming-report.pdf",
|
||||
size=4096,
|
||||
workspace=workspace,
|
||||
)
|
||||
self.assertTrue(receive_file["received"])
|
||||
self.assertEqual(4096, receive_file["file_event"]["attachments"][0]["size"])
|
||||
self.assertEqual(
|
||||
"frmP2PChat.ProccessRecvMessage -> UC_FileReceive",
|
||||
receive_file["file_event"]["ui_hook"],
|
||||
)
|
||||
|
||||
conversation = mcp_api.offline_lab_read_conversation(user_jid, workspace=workspace)
|
||||
self.assertEqual(4, conversation["conversation"]["message_count"])
|
||||
self.assertEqual(4, len(conversation["conversation"]["messages"]))
|
||||
|
||||
status = mcp_api.offline_lab_status(workspace=workspace)
|
||||
self.assertTrue(status["started"])
|
||||
self.assertEqual(4, status["message_count"])
|
||||
|
||||
def test_offline_lab_status_before_start_is_actionable(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
status = mcp_api.offline_lab_status(workspace=str(Path(tmp) / "missing"))
|
||||
|
||||
self.assertFalse(status["started"])
|
||||
self.assertEqual("call offline_lab_start", status["next_step"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
92
tests/test_real_lab.py
Normal file
92
tests/test_real_lab.py
Normal file
@@ -0,0 +1,92 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import unittest
|
||||
|
||||
from isphere_ai_bridge import real_lab
|
||||
|
||||
|
||||
class RealLoggedInLabTests(unittest.TestCase):
|
||||
def test_plan_tree_reports_ready_and_blocked_phases(self) -> None:
|
||||
tree = real_lab.plan_tree()
|
||||
|
||||
self.assertEqual("real-loggedin-planning", tree["mode"])
|
||||
self.assertEqual("A", tree["phases"][0]["id"])
|
||||
self.assertEqual("ready-now", tree["phases"][0]["status"])
|
||||
self.assertTrue(
|
||||
any(phase["status"] == "blocked-waiting-real-data" for phase in tree["phases"])
|
||||
)
|
||||
|
||||
def test_required_files_checklist_has_expected_sections(self) -> None:
|
||||
checklist = real_lab.required_files_checklist()
|
||||
|
||||
section_ids = {section["id"] for section in checklist["sections"]}
|
||||
self.assertTrue({"install_dir", "user_data", "logged_in_window"}.issubset(section_ids))
|
||||
self.assertEqual("copy-later", checklist["recommended_timing"])
|
||||
|
||||
def test_action_contract_requires_approval_for_send_and_file_upload(self) -> None:
|
||||
contract = real_lab.action_contract()
|
||||
|
||||
actions = {action["name"]: action for action in contract["actions"]}
|
||||
self.assertFalse(actions["real_lab_search_contacts"]["requires_approval"])
|
||||
self.assertTrue(actions["real_lab_send_after_approval"]["requires_approval"])
|
||||
self.assertTrue(actions["real_lab_send_file_after_approval"]["requires_approval"])
|
||||
self.assertEqual("no-login-automation", contract["hard_limits"][0])
|
||||
|
||||
def test_status_and_scan_windows_are_structured_without_real_client(self) -> None:
|
||||
status = real_lab.status()
|
||||
scan = real_lab.scan_windows()
|
||||
|
||||
self.assertIn("ready", status)
|
||||
self.assertIn("process_candidates", status)
|
||||
self.assertIn("next_step", status)
|
||||
self.assertIn("windows", scan)
|
||||
self.assertIn("next_step", scan)
|
||||
|
||||
def test_win_helper_version_and_self_check_are_structured(self) -> None:
|
||||
built = real_lab.build_win_helper()
|
||||
self.assertTrue(built["ok"], built)
|
||||
|
||||
version = real_lab.win_helper_version()
|
||||
self.assertTrue(version["ok"], version)
|
||||
self.assertEqual("version", version["op"])
|
||||
self.assertEqual("ISphereWinHelper", version["data"]["helper_name"])
|
||||
|
||||
self_check = real_lab.win_helper_self_check()
|
||||
self.assertTrue(self_check["ok"], self_check)
|
||||
self.assertEqual("self_check", self_check["op"])
|
||||
self.assertIn("uia_available", self_check["data"])
|
||||
|
||||
def test_win_helper_scan_and_missing_uia_dump_are_structured(self) -> None:
|
||||
built = real_lab.build_win_helper()
|
||||
self.assertTrue(built["ok"], built)
|
||||
|
||||
scan = real_lab.win_helper_scan_windows(include_all_visible=True)
|
||||
self.assertTrue(scan["ok"], scan)
|
||||
self.assertEqual("scan_windows", scan["op"])
|
||||
self.assertIsInstance(scan["data"]["windows"], list)
|
||||
|
||||
dump = real_lab.win_helper_dump_uia("0x0")
|
||||
self.assertFalse(dump["ok"], dump)
|
||||
self.assertIn(dump["error"]["code"], {"WINDOW_NOT_FOUND", "UIA_DUMP_FAILED"})
|
||||
|
||||
def test_real_lab_tools_are_registered(self) -> None:
|
||||
from isphere_ai_bridge.server import mcp
|
||||
|
||||
names = [tool.name for tool in asyncio.run(mcp.list_tools())]
|
||||
required = {
|
||||
"real_lab_plan_tree",
|
||||
"real_lab_required_files",
|
||||
"real_lab_action_contract",
|
||||
"real_lab_status",
|
||||
"real_lab_scan_windows",
|
||||
"real_lab_win_helper_version",
|
||||
"real_lab_win_helper_self_check",
|
||||
"real_lab_win_helper_scan_windows",
|
||||
"real_lab_win_helper_dump_uia",
|
||||
}
|
||||
self.assertTrue(required.issubset(set(names)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user