"""See & drive tools with fake QMP; screendump writes a synthetic PNG.""" import struct import pytest from fastmcp import Client from fastmcp.exceptions import ToolError from conftest import FakeQMPClient, result_data, write_registry from mcqemu.server import mcp from test_lifecycle import seeded_record def fake_png(width: int, height: int) -> bytes: """Just enough PNG for signature + IHDR dimension parsing.""" return ( b"\x89PNG\r\n\x1a\n" + b"\x00\x00\x00\x0dIHDR" + struct.pack(">II", width, height) + b"\x08\x06\x00\x00\x00" + b"\x00" * 16 ) def arm_screendump(width=640, height=480): def handler(args): with open(args["filename"], "wb") as f: f.write(fake_png(width, height)) return {} FakeQMPClient.responses["screendump"] = handler async def test_screenshot_returns_png_image(dirs, fake_qmp): arm_screendump() write_registry(dirs, seeded_record(dirs, "vm1")) async with Client(mcp) as client: result = await client.call_tool("vm_screenshot", {"name": "vm1"}) block = result.content[0] assert block.type == "image" assert block.mimeType == "image/png" call = next(a for c, a in FakeQMPClient.calls if c == "screendump") assert call["format"] == "png" async def test_send_keys_chords(dirs, fake_qmp): FakeQMPClient.responses["send-key"] = {} write_registry(dirs, seeded_record(dirs, "vm1")) async with Client(mcp) as client: data = result_data( await client.call_tool( "vm_send_keys", {"name": "vm1", "keys": ["ctrl-alt-f2", "enter"], "delay_ms": 0} ) ) assert data["keys_sent"] == 2 sent = [a["keys"] for c, a in FakeQMPClient.calls if c == "send-key"] assert sent[0] == [ {"type": "qcode", "data": "ctrl"}, {"type": "qcode", "data": "alt"}, {"type": "qcode", "data": "f2"}, ] assert sent[1] == [{"type": "qcode", "data": "ret"}] async def test_send_keys_unknown_key(dirs, fake_qmp): write_registry(dirs, seeded_record(dirs, "vm1")) async with Client(mcp) as client: with pytest.raises(ToolError, match="Unknown key"): await client.call_tool("vm_send_keys", {"name": "vm1", "keys": ["warpdrive"]}) async def test_type_text_with_shift_and_enter(dirs, fake_qmp): FakeQMPClient.responses["send-key"] = {} write_registry(dirs, seeded_record(dirs, "vm1")) async with Client(mcp) as client: data = result_data( await client.call_tool( "vm_type_text", {"name": "vm1", "text": "Hi!", "enter": True, "delay_ms": 0} ) ) assert data["characters_typed"] == 4 # H, i, !, Enter sent = [a["keys"] for c, a in FakeQMPClient.calls if c == "send-key"] assert sent[0] == [{"type": "qcode", "data": "shift"}, {"type": "qcode", "data": "h"}] assert sent[1] == [{"type": "qcode", "data": "i"}] assert sent[2] == [{"type": "qcode", "data": "shift"}, {"type": "qcode", "data": "1"}] assert sent[3] == [{"type": "qcode", "data": "ret"}] async def test_click_scales_to_abs_space(dirs, fake_qmp): arm_screendump(width=640, height=480) FakeQMPClient.responses["input-send-event"] = {} write_registry(dirs, seeded_record(dirs, "vm1")) async with Client(mcp) as client: data = result_data(await client.call_tool("vm_click", {"name": "vm1", "x": 320, "y": 240})) assert "left at (320, 240)" in data["clicked"] events = [a["events"] for c, a in FakeQMPClient.calls if c == "input-send-event"] move = events[0] assert move[0]["data"] == {"axis": "x", "value": int(320 * 32767 / 639)} assert move[1]["data"] == {"axis": "y", "value": int(240 * 32767 / 479)} assert events[1][0]["data"] == {"down": True, "button": "left"} assert events[2][0]["data"] == {"down": False, "button": "left"} async def test_click_outside_display_rejected(dirs, fake_qmp): arm_screendump(width=640, height=480) write_registry(dirs, seeded_record(dirs, "vm1")) async with Client(mcp) as client: with pytest.raises(ToolError, match="outside"): await client.call_tool("vm_click", {"name": "vm1", "x": 9999, "y": 10}) async def test_serial_read_tail(dirs, fake_qmp): record = seeded_record(dirs, "vm1", serial_log=str(dirs.state / "serial.log")) (dirs.state / "serial.log").write_text("\n".join(f"line{i}" for i in range(100))) write_registry(dirs, record) async with Client(mcp) as client: data = result_data( await client.call_tool("vm_serial_read", {"name": "vm1", "tail_lines": 3}) ) assert data["total_lines"] == 100 assert data["tail"] == "line97\nline98\nline99"