summaryrefslogtreecommitdiff
path: root/tests/test_server.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_server.py')
-rw-r--r--tests/test_server.py323
1 files changed, 320 insertions, 3 deletions
diff --git a/tests/test_server.py b/tests/test_server.py
index e04b22b..26d01b7 100644
--- a/tests/test_server.py
+++ b/tests/test_server.py
@@ -28,6 +28,7 @@ class McpServerTests(unittest.TestCase):
devices={"test": DeviceConfig(name="test", ssh_target="root@test")},
paths=PathConfig(
git_root=root,
+ obs_root=root / "OBS",
ssh_config=root / "ssh_config",
build_sailfishos=root / "tools" / "build_sailfishos.py",
osc_api_alias="jolla",
@@ -62,6 +63,10 @@ class McpServerTests(unittest.TestCase):
names = {tool["name"] for tool in response["result"]["tools"]}
self.assertIn("sailfish_device_topmost_pid", names)
self.assertIn("sailfish_device_touch", names)
+ self.assertIn("sailfish_device_touch_workflow", names)
+ self.assertIn("sailfish_device_user_session_command", names)
+ self.assertIn("sailfish_device_browser_launch", names)
+ self.assertIn("sailfish_sdk_refresh_metadata", names)
self.assertIn("sailfish_qml_check_translator_ternaries", names)
def test_qml_ternary_checker_reports_inline_ternary_qstrid(self) -> None:
@@ -149,6 +154,7 @@ class McpServerTests(unittest.TestCase):
self.assertEqual(_device_home_path(config.devices["device"]), "/home/defaultuser")
self.assertNotIn("ssh_config", config.devices["device"].public_dict())
self.assertEqual(config.paths.ssh_config, Path.home() / ".ssh" / "config")
+ self.assertEqual(config.paths.obs_root, Path.home() / "OBS")
self.assertEqual(config.paths.build_sailfishos, BUNDLED_BUILD_HELPER)
self.assertIsNone(config.paths.local_sdk)
self.assertTrue(config.paths.build_sailfishos.exists())
@@ -158,6 +164,7 @@ class McpServerTests(unittest.TestCase):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
local_sdk = root / "sdk-chroot"
+ obs_root = root / "OBS"
ssh_config = root / "ssh_config"
config_path = root / "config.json"
config_path.write_text(
@@ -166,6 +173,7 @@ class McpServerTests(unittest.TestCase):
"devices": {"phone": "root@phone"},
"paths": {
"git_root": str(root),
+ "obs_root": str(obs_root),
"ssh_config": str(ssh_config),
"local_sdk": str(local_sdk),
},
@@ -175,6 +183,7 @@ class McpServerTests(unittest.TestCase):
)
config = load_config(config_path)
self.assertEqual(config.paths.local_sdk, local_sdk)
+ self.assertEqual(config.paths.obs_root, obs_root)
self.assertEqual(config.paths.ssh_config, ssh_config)
self.assertEqual(config.devices["phone"].release, "")
@@ -198,6 +207,35 @@ class McpServerTests(unittest.TestCase):
config = load_config(config_path)
self.assertEqual(config.devices["phone"].release, "devel")
+ def test_repo_find_allows_obs_root_paths(self) -> None:
+ with tempfile.TemporaryDirectory() as tmp:
+ root = Path(tmp)
+ obs_checkout = root / "OBS" / "home:example" / "browser"
+ obs_checkout.mkdir(parents=True)
+ server = self.make_server(root)
+ with (
+ patch("sailfish_devel_mcp.tools.shutil.which", return_value="/usr/bin/rg"),
+ patch("sailfish_devel_mcp.tools.run") as mocked_run,
+ ):
+ mocked_run.return_value = CommandResult(("rg",), 0, "match\n", "")
+ response = server.handle(
+ {
+ "jsonrpc": "2.0",
+ "id": 6,
+ "method": "tools/call",
+ "params": {
+ "name": "sailfish_repo_find",
+ "arguments": {
+ "path": str(obs_checkout),
+ "query": "Package",
+ },
+ },
+ }
+ )
+ self.assertFalse(response["result"].get("isError", False))
+ argv = list(mocked_run.call_args.args[0])
+ self.assertEqual(argv[-1], str(obs_checkout))
+
def test_screenshot_prepare_command_uses_home_ownership(self) -> None:
device = DeviceConfig(name="test", ssh_target="root@test")
command = _screenshot_prepare_command(
@@ -288,6 +326,169 @@ class McpServerTests(unittest.TestCase):
remote = mocked_run.call_args.args[0][-1]
self.assertIn("evdev_trace -i", remote)
+ def test_device_touch_workflow_screenshots_discovers_and_taps(self) -> None:
+ with tempfile.TemporaryDirectory() as tmp:
+ server = self.make_server(Path(tmp))
+ with patch("sailfish_devel_mcp.tools.run") as mocked_run:
+ mocked_run.side_effect = [
+ CommandResult(("ssh", "root@test"), 0, "screenshot\n", ""),
+ CommandResult(("ssh", "root@test"), 0, "inputs\n", ""),
+ CommandResult(("ssh", "root@test"), 0, "tap=ok\n", ""),
+ ]
+ response = server.handle(
+ {
+ "jsonrpc": "2.0",
+ "id": 9,
+ "method": "tools/call",
+ "params": {
+ "name": "sailfish_device_touch_workflow",
+ "arguments": {"action": "tap", "x": 12, "y": 34},
+ },
+ }
+ )
+ self.assertFalse(response["result"].get("isError", False))
+ self.assertEqual(
+ response["result"]["structuredContent"]["step_order"],
+ ["screenshot_before", "touchscreen_discovery", "touch"],
+ )
+ remotes = [call.args[0][-1] for call in mocked_run.call_args_list]
+ self.assertIn("saveScreenshot", remotes[0])
+ self.assertIn("/proc/bus/input/devices", remotes[1])
+ self.assertIn("X=12", remotes[2])
+ self.assertIn("Y=34", remotes[2])
+
+ def test_user_session_command_can_run_as_defaultuser(self) -> None:
+ with tempfile.TemporaryDirectory() as tmp:
+ server = self.make_server(Path(tmp))
+ with patch("sailfish_devel_mcp.tools.run") as mocked_run:
+ mocked_run.return_value = CommandResult(("ssh", "root@test"), 0, "", "")
+ response = server.handle(
+ {
+ "jsonrpc": "2.0",
+ "id": 10,
+ "method": "tools/call",
+ "params": {
+ "name": "sailfish_device_user_session_command",
+ "arguments": {
+ "command": ["systemctl", "--user", "status", "app.service"],
+ "run_as_user": True,
+ },
+ },
+ }
+ )
+ self.assertFalse(response["result"].get("isError", False))
+ remote = mocked_run.call_args.args[0][-1]
+ self.assertIn("runuser -u defaultuser -- env", remote)
+ self.assertIn("XDG_RUNTIME_DIR=/run/user/100000", remote)
+ self.assertIn("DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/100000/dbus/user_bus_socket", remote)
+ self.assertIn("systemctl --user status app.service", remote)
+
+ def test_browser_launch_uses_display_env_and_reports_topmost_pid(self) -> None:
+ with tempfile.TemporaryDirectory() as tmp:
+ server = self.make_server(Path(tmp))
+ with patch("sailfish_devel_mcp.tools.run") as mocked_run:
+ mocked_run.return_value = CommandResult(("ssh", "root@test"), 0, "", "")
+ response = server.handle(
+ {
+ "jsonrpc": "2.0",
+ "id": 11,
+ "method": "tools/call",
+ "params": {
+ "name": "sailfish_device_browser_launch",
+ "arguments": {"url": "https://example.com"},
+ },
+ }
+ )
+ self.assertFalse(response["result"].get("isError", False))
+ remote = mocked_run.call_args.args[0][-1]
+ self.assertIn("WAYLAND_DISPLAY=../../display/wayland-0", remote)
+ self.assertIn("/usr/bin/invoker --type=browser,silica-qt5 -A --", remote)
+ self.assertIn("/usr/bin/sailfish-browser https://example.com", remote)
+ self.assertIn("privateTopmostWindowProcessId", remote)
+
+ def test_obs_buildlog_uses_nostream_api_by_default(self) -> None:
+ with tempfile.TemporaryDirectory() as tmp:
+ server = self.make_server(Path(tmp))
+ with patch("sailfish_devel_mcp.tools.run") as mocked_run:
+ mocked_run.return_value = CommandResult(("osc",), 0, "", "")
+ response = server.handle(
+ {
+ "jsonrpc": "2.0",
+ "id": 12,
+ "method": "tools/call",
+ "params": {
+ "name": "sailfish_obs_buildlog",
+ "arguments": {
+ "project": "home:example",
+ "package": "browser",
+ "repository": "5.0.0",
+ "arch": "aarch64",
+ },
+ },
+ }
+ )
+ self.assertFalse(response["result"].get("isError", False))
+ argv = list(mocked_run.call_args.args[0])
+ self.assertEqual(argv[:3], ["osc", "-A", "jolla"])
+ self.assertEqual(argv[3], "api")
+ self.assertEqual(
+ argv[4],
+ "/build/home%3Aexample/5.0.0/aarch64/browser/_log?nostream=1",
+ )
+
+ def test_sdk_refresh_metadata_uses_local_sdk_main_target(self) -> None:
+ with tempfile.TemporaryDirectory() as tmp:
+ root = Path(tmp)
+ local_sdk = root / "sdks" / "sfossdk" / "sdk-chroot"
+ local_sdk.parent.mkdir(parents=True)
+ local_sdk.touch()
+ self.write_fake_target(
+ local_sdk.parent,
+ "aarch64",
+ ssu_release="live",
+ version_id="5.2.0.4",
+ )
+ config = Config(
+ path=None,
+ default_device="phone",
+ devices={
+ "phone": DeviceConfig(
+ name="phone",
+ ssh_target="root@phone",
+ architecture="aarch64",
+ release="live",
+ )
+ },
+ paths=PathConfig(
+ git_root=root,
+ ssh_config=root / "ssh_config",
+ local_sdk=local_sdk,
+ ),
+ )
+ server = McpServer(config)
+ with patch("sailfish_devel_mcp.tools.run") as mocked_run:
+ mocked_run.return_value = CommandResult(("docker",), 0, "", "")
+ response = server.handle(
+ {
+ "jsonrpc": "2.0",
+ "id": 13,
+ "method": "tools/call",
+ "params": {
+ "name": "sailfish_sdk_refresh_metadata",
+ "arguments": {"device": "phone"},
+ },
+ }
+ )
+ self.assertFalse(response["result"].get("isError", False))
+ argv = list(mocked_run.call_args.args[0])
+ self.assertEqual(argv[:3], ["docker", "run", "--rm"])
+ wrapper = argv[-1]
+ self.assertIn("sb2 -t aarch64.default -m sdk-install -R zypper ref", wrapper)
+ self.assertEqual(
+ response["result"]["structuredContent"]["main_target"],
+ "aarch64.default",
+ )
+
def test_build_defaults_prefer_configured_local_sdk(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
@@ -315,8 +516,12 @@ class McpServerTests(unittest.TestCase):
),
)
server = McpServer(config)
- with patch("sailfish_devel_mcp.tools.run") as mocked_run:
- mocked_run.return_value = CommandResult(("python3", str(helper)), 0, "", "")
+ with patch("sailfish_devel_mcp.tools._start_background_command") as mocked_start:
+ mocked_start.return_value = {
+ "job_id": "build-test",
+ "status_path": str(root / "status.json"),
+ "log_path": str(root / "build.log"),
+ }
response = server.handle(
{
"jsonrpc": "2.0",
@@ -329,13 +534,108 @@ class McpServerTests(unittest.TestCase):
}
)
self.assertFalse(response["result"].get("isError", False))
- argv = list(mocked_run.call_args.args[0])
+ argv = list(mocked_start.call_args.args[1])
self.assertIn("--local-sdk", argv)
self.assertIn(str(local_sdk), argv)
self.assertIn("--release", argv)
self.assertIn("live", argv)
self.assertIn("--arch", argv)
self.assertIn("aarch64", argv)
+ self.assertEqual(
+ response["result"]["structuredContent"]["job_id"],
+ "build-test",
+ )
+
+ def test_build_without_release_defaults_to_live_local_sdk(self) -> None:
+ with tempfile.TemporaryDirectory() as tmp:
+ root = Path(tmp)
+ (root / "rpm").mkdir()
+ (root / "rpm" / "sample.spec").write_text("Name: sample\n", encoding="utf-8")
+ helper = root / "build_sailfishos.py"
+ local_sdk = root / "sdk-chroot"
+ helper.write_text("#!/usr/bin/env python3\n", encoding="utf-8")
+ config = Config(
+ path=None,
+ default_device="phone",
+ devices={"phone": DeviceConfig(name="phone", ssh_target="root@phone")},
+ paths=PathConfig(
+ git_root=root,
+ ssh_config=root / "ssh_config",
+ build_sailfishos=helper,
+ local_sdk=local_sdk,
+ ),
+ )
+ server = McpServer(config)
+ with patch("sailfish_devel_mcp.tools._start_background_command") as mocked_start:
+ mocked_start.return_value = {
+ "job_id": "build-test",
+ "status_path": str(root / "status.json"),
+ "log_path": str(root / "build.log"),
+ }
+ response = server.handle(
+ {
+ "jsonrpc": "2.0",
+ "id": 14,
+ "method": "tools/call",
+ "params": {
+ "name": "sailfish_build_rpm",
+ "arguments": {
+ "project_path": str(root),
+ "arch": "aarch64",
+ },
+ },
+ }
+ )
+ self.assertFalse(response["result"].get("isError", False))
+ argv = list(mocked_start.call_args.args[1])
+ self.assertIn("--local-sdk", argv)
+ self.assertIn(str(local_sdk), argv)
+ self.assertIn("--release", argv)
+ self.assertIn("live", argv)
+ self.assertIn("--arch", argv)
+ self.assertIn("aarch64", argv)
+
+ def test_build_wait_true_uses_synchronous_runner(self) -> None:
+ with tempfile.TemporaryDirectory() as tmp:
+ root = Path(tmp)
+ (root / "rpm").mkdir()
+ (root / "rpm" / "sample.spec").write_text("Name: sample\n", encoding="utf-8")
+ helper = root / "build_sailfishos.py"
+ helper.write_text("#!/usr/bin/env python3\n", encoding="utf-8")
+ config = Config(
+ path=None,
+ default_device="phone",
+ devices={"phone": DeviceConfig(name="phone", ssh_target="root@phone")},
+ paths=PathConfig(
+ git_root=root,
+ ssh_config=root / "ssh_config",
+ build_sailfishos=helper,
+ ),
+ )
+ server = McpServer(config)
+ with patch("sailfish_devel_mcp.tools.run") as mocked_run:
+ mocked_run.return_value = CommandResult(("python3", str(helper)), 0, "ok", "")
+ response = server.handle(
+ {
+ "jsonrpc": "2.0",
+ "id": 15,
+ "method": "tools/call",
+ "params": {
+ "name": "sailfish_build_rpm",
+ "arguments": {
+ "project_path": str(root),
+ "arch": "aarch64",
+ "wait": True,
+ },
+ },
+ }
+ )
+
+ self.assertFalse(response["result"].get("isError", False))
+ self.assertEqual(response["result"]["structuredContent"]["returncode"], 0)
+ argv = list(mocked_run.call_args.args[0])
+ self.assertIn("--arch", argv)
+ self.assertIn("aarch64", argv)
def write_fake_target(
self,
@@ -413,6 +713,23 @@ class McpServerTests(unittest.TestCase):
self.assertIsNone(missing)
self.assertIsNone(exact_mismatch)
+ def test_build_helper_local_sdk_default_ignores_workflow_release(self) -> None:
+ with tempfile.TemporaryDirectory() as tmp:
+ root = Path(tmp)
+ (root / "rpm").mkdir()
+ (root / "rpm" / "sample.spec").write_text("Name: sample\n", encoding="utf-8")
+ workflows = root / ".github" / "workflows"
+ workflows.mkdir(parents=True)
+ (workflows / "build.yml").write_text("env:\n RELEASE: 5.0.0\n", encoding="utf-8")
+
+ with patch.dict(os.environ, {}, clear=True):
+ self.assertEqual(
+ build_sailfishos.requested_release((root,), None),
+ "5.0.0",
+ )
+ self.assertIsNone(build_sailfishos.explicit_requested_release(None))
+ self.assertEqual(build_sailfishos.local_sdk_requested_release(None), "live")
+
if __name__ == "__main__":
unittest.main()