package mcpclient import ( "context" "os" "path/filepath" "strings" "testing" "time" "github.com/modelcontextprotocol/go-sdk/mcp" ) func TestMobilePresetPersistsAndHonorsDisabled(t *testing.T) { s, _ := testService(t) if err := s.ensureMobile(); err != nil { t.Fatal(err) } if err := s.ensureMobile(); err != nil { t.Fatal(err) } views, err := s.ListServers() if err != nil || len(views) != 1 || views[0].Config.Transport != "mobile" { t.Fatalf("preset: %+v %v", views, err) } if err := s.SetEnabled(mobileServerID, false); err != nil { t.Fatal(err) } restored := newService(s.path, memorySecrets{}) if err := restored.ensureMobile(); err != nil { t.Fatal(err) } views, err = restored.ListServers() if err != nil || len(views) != 1 || views[0].Config.Enabled { t.Fatalf("disabled preference lost: %+v %v", views, err) } if err := s.DeleteServer(mobileServerID); err == nil { t.Fatal("deleted system preset") } c, err := normalize(ServerConfig{Name: "Mobile", Transport: "mobile", TimeoutSeconds: 30, Command: "unexpected", URL: "https://invalid.test", Args: []string{"bad"}}) if err != nil || c.Command != "" || c.URL != "" || len(c.Args) != 0 { t.Fatalf("launch parameters persisted: %+v %v", c, err) } if err := validateSecrets(c, Secrets{Env: map[string]string{"NODE_OPTIONS": "--inspect"}}); err == nil { t.Fatal("accepted builtin credentials") } } func TestBuiltinMobileOnlyExposesLocalDriverTools(t *testing.T) { c := ServerConfig{Transport: "mobile"} for _, name := range []string{"mobile_login_to_cloud_provider", "mobile_list_remote_devices", "mobile_allocate_remote_device", "mobile_release_remote_device", "mobile_batch_commands", "mobile_get_foreground_app", "mobile_future_cloud_tool"} { if supportedTool(c, name) { t.Errorf("builtin exposes unsupported tool %s", name) } if !supportedTool(ServerConfig{Transport: "stdio"}, name) { t.Errorf("custom server unexpectedly filtered: %s", name) } } if got := toolDescription(c, "mobile_list_available_devices", "Use mobile_list_remote_devices"); strings.Contains(got, "mobile_list_remote_devices") { t.Fatal("local discovery still recommends cloud devices") } } func TestBundledMobile(t *testing.T) { if os.Getenv("ITUOKE_TEST_MOBILE") != "1" { t.Skip("stage official runtime and set ITUOKE_TEST_MOBILE=1") } t.Chdir(filepath.Join("..", "..")) t.Setenv("PATH", t.TempDir()) t.Setenv("NODE_OPTIONS", "--invalid-parent-option") s, _ := testService(t) if err := s.ensureMobile(); err != nil { t.Fatal(err) } views, err := s.ListServers() if err != nil { t.Fatal(err) } ctx, cancel := context.WithTimeout(context.Background(), 40*time.Second) defer cancel() result := s.TestServer(ctx, ServerInput{Config: views[0].Config}) if !result.OK { t.Fatal(result.Message) } names := map[string]bool{} for _, tool := range result.Tools { names[tool.Name] = true if !supportedTool(views[0].Config, tool.Name) { t.Fatalf("discovery exposes unsupported tool %s", tool.Name) } } for _, name := range []string{"mobile_list_available_devices", "mobile_take_screenshot", "mobile_click_on_screen_at_coordinates", "mobile_swipe_on_screen", "mobile_type_keys"} { if !names[name] { t.Fatalf("missing tool %s", name) } } if err := withSession(ctx, views[0].Config, Secrets{}, func(session *mcp.ClientSession) error { result, err := session.CallTool(ctx, &mcp.CallToolParams{Name: "mobile_list_available_devices", Arguments: map[string]any{}}) if err != nil { return err } if result.IsError { t.Fatalf("device discovery failed: %+v", result.Content) } return nil }); err != nil { t.Fatal(err) } toolset, err := OpenTools(ctx, s, nil) if err != nil { t.Fatal(err) } defer toolset.Close() if len(toolset.Tools()) < 5 { t.Fatal("tools not available to Agent") } for _, tool := range toolset.Tools() { if !supportedTool(views[0].Config, tool.Tool) { t.Fatalf("Agent exposes unsupported tool %s", tool.Tool) } } raw, _ := os.ReadFile(s.path) if strings.Contains(string(raw), "node_modules") || strings.Contains(string(raw), "Applications") { t.Fatal("machine-specific paths saved") } t.Logf("bundled Mobile Next: %d tools; device discovery succeeded without system Node/ADB", len(result.Tools)) }