Files
galaxy/crates/integration/src/test/remote_server.rs
T

288 lines
13 KiB
Rust

use std::collections::HashMap;
use std::time::Duration;
use galaxyui_core::integration::TestStep;
use settings::Setting as _;
use warp::features::FeatureFlag;
use warp::integration_testing::remote_server::{
assert_command_executor_is_remote_server, assert_remote_server_connected,
assert_remote_server_has_navigated, assert_remote_server_loaded_repo_metadata_directory,
load_repo_metadata_directory_via_remote_server, record_remote_server_lazy_load_events,
record_remote_server_navigation_events, wait_for_remote_server_ready,
write_file_via_remote_server,
};
use warp::integration_testing::step::new_step_with_default_assertions;
use warp::integration_testing::subshell::{
enter_remote_server_ssh_command, enter_ssh_password, setup_gcloud_sdk,
wait_for_remote_server_password_prompt,
};
use warp::integration_testing::terminal::util::{
current_shell_starter_and_version, ExpectedExitStatus,
};
use warp::integration_testing::terminal::{
execute_command_for_single_terminal_in_tab, run_completer,
wait_until_bootstrapped_single_pane_for_tab,
};
use warp::terminal::shell::ShellType;
use warp::terminal::warpify::settings::{SshExtensionInstallMode, SshExtensionInstallModeSetting};
use super::{new_builder, Builder};
/// Common builder configuration for remote server tests: enables the
/// `SshRemoteServer` feature flag for these tests and sets the install mode to
/// `AlwaysInstall` so the binary check → connect flow runs without user
/// interaction.
fn remote_server_builder() -> Builder {
FeatureFlag::SshRemoteServer.set_enabled(true);
new_builder()
.set_should_run_test(|| {
if !cfg!(target_os = "linux") {
return false;
}
let (starter, _) = current_shell_starter_and_version();
starter.shell_type() != ShellType::PowerShell
})
.with_user_defaults(HashMap::from([(
SshExtensionInstallModeSetting::storage_key().to_owned(),
serde_json::to_string(&SshExtensionInstallMode::AlwaysInstall)
.expect("Can serialize SshExtensionInstallMode"),
)]))
}
/// Appends the common SSH → remote server connection steps to a builder.
fn with_ssh_connect_steps(builder: Builder, shell: &'static str) -> Builder {
builder
.with_step(wait_until_bootstrapped_single_pane_for_tab(0))
.with_step(setup_gcloud_sdk())
.with_step(enter_remote_server_ssh_command(shell))
.with_step(wait_for_remote_server_password_prompt(0, shell))
.with_step(enter_ssh_password().set_post_step_pause(Duration::from_millis(250)))
.with_step(wait_for_remote_server_ready(0))
.with_step(wait_until_bootstrapped_single_pane_for_tab(0))
}
// ---------------------------------------------------------------------------
// Test A — Connection and handshake
// ---------------------------------------------------------------------------
macro_rules! generate_remote_server_connect_test {
($fn_name:ident, $shell:literal) => {
/// Validates SSH → binary check → proto handshake → executor wiring.
pub fn $fn_name() -> Builder {
let builder = with_ssh_connect_steps(remote_server_builder(), $shell);
builder
.with_step(
new_step_with_default_assertions("Assert remote server session is connected")
.add_assertion(assert_remote_server_connected(0)),
)
.with_step(
new_step_with_default_assertions(
"Assert command executor is RemoteServerCommandExecutor",
)
.add_assertion(assert_command_executor_is_remote_server(0)),
)
}
};
}
generate_remote_server_connect_test!(test_remote_server_connect_bash, "bash");
generate_remote_server_connect_test!(test_remote_server_connect_zsh, "zsh");
// ---------------------------------------------------------------------------
// Test B — Repo metadata (navigate to a git repo)
// ---------------------------------------------------------------------------
/// Validates the full navigate-to-directory flow:
/// SSH → create git repo on remote → cd into it → NavigatedToDirectory response
/// received → RepoMetadataSnapshot pushed with non-empty tree.
pub fn test_remote_server_navigate_to_repo() -> Builder {
let builder = with_ssh_connect_steps(remote_server_builder(), "bash");
builder
.with_step(record_remote_server_navigation_events())
.with_step(execute_command_for_single_terminal_in_tab(
0,
"mkdir -p /tmp/warp-test-repo && cd /tmp/warp-test-repo && git init -b main && git config user.email test@test.com && git config user.name TestUser && touch file && git add file && git commit -m init".into(),
ExpectedExitStatus::Success,
(),
))
.with_step(execute_command_for_single_terminal_in_tab(
0,
"cd /tmp/warp-test-repo".into(),
ExpectedExitStatus::Success,
(),
))
// Assert the remote server received a successful navigation response
// for the expected repo path.
.with_step(
new_step_with_default_assertions(
"Assert remote server has navigated to directory",
)
.set_timeout(Duration::from_secs(15))
.add_named_assertion_with_data_from_prior_step(
"remote server navigated to expected repo path",
assert_remote_server_has_navigated(0, "/tmp/warp-test-repo"),
),
)
// Verify the connection is still healthy after navigation.
.with_step(
new_step_with_default_assertions(
"Assert host has tracked sessions after navigation",
)
.add_assertion(assert_remote_server_connected(0)),
)
}
// ---------------------------------------------------------------------------
// Test C — Completions routing
// ---------------------------------------------------------------------------
/// Validates that completions are routed through the `RemoteServerCommandExecutor`
/// (the `RunCommand` proto path) rather than falling back to the legacy
/// `RemoteCommandExecutor`.
pub fn test_remote_server_completions() -> Builder {
let builder = with_ssh_connect_steps(remote_server_builder(), "bash");
builder
.with_step(execute_command_for_single_terminal_in_tab(
0,
"touch /tmp/warp-rs-completion-target".into(),
ExpectedExitStatus::Success,
(),
))
// Trigger the actual tab-completion request path for a remote file.
.with_step(
run_completer(0, "cat /tmp/warp-rs-completion-t").set_timeout(Duration::from_secs(15)),
)
// Verify the executor is still the remote server executor after
// completions have been triggered.
.with_step(
new_step_with_default_assertions(
"Assert command executor is RemoteServerCommandExecutor after completions",
)
.add_assertion(assert_command_executor_is_remote_server(0)),
)
}
// ---------------------------------------------------------------------------
// Test D — File read/write/delete via proto client API
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Test E — Lazy loading repo metadata directory
// ---------------------------------------------------------------------------
/// Validates the `LoadRepoMetadataDirectory` proto round-trip:
/// navigate to a git repo → create a subdirectory → call
/// `load_remote_repo_metadata_directory` for that subdirectory → verify
/// the request completes without error (the response flows through the
/// manager's `RepoMetadataDirectoryLoaded` event).
pub fn test_remote_server_lazy_load_directory() -> Builder {
let builder = with_ssh_connect_steps(remote_server_builder(), "bash");
builder
.with_step(record_remote_server_navigation_events())
.with_step(record_remote_server_lazy_load_events())
// Create a git repo with a subdirectory on the remote.
.with_step(execute_command_for_single_terminal_in_tab(
0,
"rm -rf /tmp/warp-lazy-repo && mkdir -p /tmp/warp-lazy-repo/subdir && cd /tmp/warp-lazy-repo && git init -b main && git config user.email test@test.com && git config user.name TestUser && touch file subdir/nested && git add . && git commit -m init".into(),
ExpectedExitStatus::Success,
(),
))
// Navigate into the repo so NavigatedToDirectory fires and the
// server indexes it.
.with_step(execute_command_for_single_terminal_in_tab(
0,
"cd /tmp/warp-lazy-repo".into(),
ExpectedExitStatus::Success,
(),
))
.with_step(
new_step_with_default_assertions(
"Wait for navigation to complete",
)
.set_timeout(Duration::from_secs(15))
.add_named_assertion_with_data_from_prior_step(
"remote server navigated to expected lazy-load repo path",
assert_remote_server_has_navigated(0, "/tmp/warp-lazy-repo"),
),
)
// Trigger lazy-load of the subdirectory via the proto API.
.with_step(
TestStep::new("Load subdirectory via LoadRepoMetadataDirectory")
.with_action(load_repo_metadata_directory_via_remote_server(
0,
"/tmp/warp-lazy-repo".to_string(),
"/tmp/warp-lazy-repo/subdir".to_string(),
))
// Give the async request a moment to complete.
.set_post_step_pause(Duration::from_secs(2)),
)
.with_step(
new_step_with_default_assertions("Assert lazy-load directory response succeeded")
.set_timeout(Duration::from_secs(15))
.add_named_assertion_with_data_from_prior_step(
"remote server loaded repo metadata directory",
assert_remote_server_loaded_repo_metadata_directory(0),
),
)
// Verify the connection is still healthy after the lazy-load.
.with_step(
new_step_with_default_assertions(
"Assert remote server still connected after lazy load",
)
.add_assertion(assert_remote_server_connected(0)),
)
// Verify the subdirectory content is accessible by reading
// the nested file through the remote server executor.
.with_step(execute_command_for_single_terminal_in_tab(
0,
"cat /tmp/warp-lazy-repo/subdir/nested && echo 'file exists'".into(),
ExpectedExitStatus::Success,
"file exists",
))
}
// ---------------------------------------------------------------------------
// Test F — File write via proto client API
// ---------------------------------------------------------------------------
/// Validates `WriteFile` through the `RemoteServerClient` proto API.
/// Uses `write_file_via_remote_server` helper to write a file on the remote
/// host, then reads it back via a shell command (which goes through
/// `RemoteServerCommandExecutor::run_command`) to confirm the content.
pub fn test_remote_server_file_operations() -> Builder {
let builder = with_ssh_connect_steps(remote_server_builder(), "bash");
builder
// Step 1: Write a file using RemoteServerClient::write_file proto API
.with_step(
TestStep::new("Write file via RemoteServerClient proto API")
.with_action(write_file_via_remote_server(
0,
"/tmp/warp-rs-test-file.txt".to_string(),
"hello from proto".to_string(),
))
// Give the async write a moment to complete
.set_post_step_pause(Duration::from_secs(2)),
)
// Step 2: Verify the file was written by reading it via shell command
// (which goes through RemoteServerCommandExecutor::run_command).
.with_step(execute_command_for_single_terminal_in_tab(
0,
"cat /tmp/warp-rs-test-file.txt".into(),
ExpectedExitStatus::Success,
"hello from proto",
))
// Step 3: Clean up
.with_step(execute_command_for_single_terminal_in_tab(
0,
"rm -f /tmp/warp-rs-test-file.txt".into(),
ExpectedExitStatus::Success,
(),
))
.with_step(
new_step_with_default_assertions(
"Assert command executor is still RemoteServerCommandExecutor after file ops",
)
.add_assertion(assert_command_executor_is_remote_server(0)),
)
}