From 007d67ff597746d3bc0720930c83d6524e7ac9e7 Mon Sep 17 00:00:00 2001 From: Val Alexander <68980965+BunsDev@users.noreply.github.com> Date: Sat, 30 May 2026 13:34:44 -0500 Subject: [PATCH] fix(tests): green warp-app on linux/macOS/windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eight failing tests in the warp-app suite split into three categories: Brand & rebrand drift — assertions still pinned to upstream "Warp" copy after the CastCodes rebrand. - context_chips: format_git_branch error string updated to "CastCodes". - settings_view::environments_page: section-header copy now expects "SHARED BY CASTCODES AND ...". Cross-platform path handling — Linux/Windows tests broke on Unix-style literals. - ai::blocklist::handoff::touched_repos: swap `/repo` and `/repo/subdir` literals for a `cfg(windows)`-aware constant so `Path::is_absolute()` holds on every OS. - lib launch_mode: gate `castcodes_app_bundle_executable_launches_gui` to `cfg(target_os = "macos")`; the bundle-detection branch it exercises is macOS-only (stub on Linux/Windows). Drift from production-code changes (OSS/fork-local + harness gating). - ai::blocklist::action_model::execute::start_agent: use "claude" so the local-harness child parses successfully and the test reaches the OrchestrationV2 gate it claims to exercise (the prior "codex" identifier no longer parses as a valid local-harness child). - drive::import: refresh the `expand_dirs` snapshot to include the three test-data fixtures added since the test was last touched (tweakcn_sample_dark, tweakcn_sample_light, ui_block_theme). - pane_group::test_start_shared_session_from_modal: temporarily override the channel to `Stable` for the duration of the test, since `open_share_session_modal` now early-returns on OSS channels where cloud services are unavailable. Adds a `#[cfg(feature = "test-util")]` `ChannelState::override_channel_for_test` RAII guard in warp_core for this; the production fork-local gate is preserved. - auth::auth_manager::initialize_user_from_auth_payload: run CSRF state validation before the hosted-auth-disabled bail. The `should_silently_ignore_stale_redirect` and `InvalidStateParameter` paths are channel-agnostic — surfacing an invalid-state error from a redirect that smuggled in a mismatched token is still correct in the OSS build, and never touches the hosted backend. This restores `test_stale_state_when_logged_out_emits_invalid_state_parameter` and `test_mismatched_state_with_different_user_uid_emits_invalid_state_parameter`. Verification: `cargo nextest run -p warp-app --no-fail-fast` → 4212 passed, 1 pre-existing failure (`settings::init::tests::test_migration_does_not_rerun_when_marker_present`, which reads `~/.cast-codes/settings.toml` from the real home dir and fails on any machine that has used CastCodes — reproduces on `main` unchanged). `cargo fmt --check` clean. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../action_model/execute/start_agent_tests.rs | 4 ++- .../blocklist/handoff/touched_repos_tests.rs | 25 +++++++++++++---- app/src/auth/auth_manager.rs | 18 ++++++++---- app/src/context_chips/display_chip_tests.rs | 2 +- app/src/drive/import/import_tests.rs | 2 +- app/src/lib.rs | 4 +++ app/src/pane_group/mod_tests.rs | 7 +++++ .../settings_view/environments_page_tests.rs | 4 +-- crates/warp_core/src/channel/state.rs | 28 +++++++++++++++++++ 9 files changed, 78 insertions(+), 16 deletions(-) diff --git a/app/src/ai/blocklist/action_model/execute/start_agent_tests.rs b/app/src/ai/blocklist/action_model/execute/start_agent_tests.rs index efd44a711..060ed3143 100644 --- a/app/src/ai/blocklist/action_model/execute/start_agent_tests.rs +++ b/app/src/ai/blocklist/action_model/execute/start_agent_tests.rs @@ -198,9 +198,11 @@ fn execute_returns_error_when_local_harness_child_requires_orchestration_v2() { let parent_conversation_id = history_model.update(&mut app, |history_model, ctx| { history_model.start_new_conversation(terminal_view_id, false, false, ctx) }); + // Use "claude" (a valid local child harness) so parsing succeeds and we + // reach the OrchestrationV2 gate this test exercises. let action = build_start_agent_action( StartAgentVersion::V2, - StartAgentExecutionMode::local_harness("codex".to_string()), + StartAgentExecutionMode::local_harness("claude".to_string()), ); let execution = executor.update(&mut app, |executor, ctx| { diff --git a/app/src/ai/blocklist/handoff/touched_repos_tests.rs b/app/src/ai/blocklist/handoff/touched_repos_tests.rs index f8e532dd5..5da7d2472 100644 --- a/app/src/ai/blocklist/handoff/touched_repos_tests.rs +++ b/app/src/ai/blocklist/handoff/touched_repos_tests.rs @@ -50,14 +50,24 @@ fn find_git_root_walks_up_to_dot_git() { assert!(root_for_outside.is_none()); } +// Use a platform-appropriate absolute path so `Path::is_absolute()` is true on +// every OS (Windows requires a drive prefix; `/repo` is only absolute on Unix). +#[cfg(windows)] +const REPO_ROOT: &str = r"C:\repo"; +#[cfg(not(windows))] +const REPO_ROOT: &str = "/repo"; +#[cfg(windows)] +const REPO_SUBDIR: &str = r"C:\repo\subdir"; +#[cfg(not(windows))] +const REPO_SUBDIR: &str = "/repo/subdir"; + #[test] fn extract_paths_ignores_cancelled_or_failed_file_edit_results() { - let cwd = "/repo"; let cancelled = action_id("cancelled-edit"); let failed = action_id("failed-edit"); let successful = action_id("successful-edit"); let exchange = exchange_with_actions( - Some(cwd), + Some(REPO_ROOT), vec![ file_edit_action(cancelled.clone(), "cancelled.txt"), file_edit_action(failed.clone(), "failed.txt"), @@ -79,7 +89,10 @@ fn extract_paths_ignores_cancelled_or_failed_file_edit_results() { assert_eq!( paths, - vec![PathBuf::from(cwd), PathBuf::from("/repo/written.txt")] + vec![ + PathBuf::from(REPO_ROOT), + PathBuf::from(REPO_ROOT).join("written.txt"), + ] ); } @@ -88,7 +101,7 @@ fn extract_paths_ignores_failed_upload_artifacts() { let failed = action_id("failed-upload"); let successful = action_id("successful-upload"); let exchange = exchange_with_actions( - Some("/repo/subdir"), + Some(REPO_SUBDIR), vec![ upload_action(failed.clone(), "../failed.log"), upload_action(successful.clone(), "../artifact.log"), @@ -116,8 +129,8 @@ fn extract_paths_ignores_failed_upload_artifacts() { assert_eq!( paths, vec![ - PathBuf::from("/repo/subdir"), - PathBuf::from("/repo/subdir/../artifact.log") + PathBuf::from(REPO_SUBDIR), + PathBuf::from(REPO_SUBDIR).join("../artifact.log"), ] ); } diff --git a/app/src/auth/auth_manager.rs b/app/src/auth/auth_manager.rs index d21909644..60442cb20 100644 --- a/app/src/auth/auth_manager.rs +++ b/app/src/auth/auth_manager.rs @@ -151,11 +151,6 @@ impl AuthManager { enforce_state_validation: bool, ctx: &mut ModelContext, ) { - if Self::hosted_auth_disabled() { - log::info!("Ignoring auth redirect: hosted auth is disabled for this channel"); - return; - } - let AuthRedirectPayload { refresh_token, user_uid, @@ -163,6 +158,11 @@ impl AuthManager { state, } = auth_payload.clone(); + // CSRF state validation runs even when hosted auth is disabled. The + // pending-state token is generated locally, so consuming/rejecting it + // never touches the hosted backend — and surfacing an invalid-state + // error from a redirect that smuggled in a mismatched token is still + // the right behavior in the OSS build. if let Some(received_state) = &state { if !self.consume_auth_state(received_state) { if self.should_silently_ignore_stale_redirect(&user_uid) { @@ -187,6 +187,14 @@ impl AuthManager { return; } + // State validation passed (or wasn't required). From here we'd actually + // contact the hosted backend, which OSS builds don't have — bail before + // touching `auth_client`. + if Self::hosted_auth_disabled() { + log::info!("Ignoring auth redirect: hosted auth is disabled for this channel"); + return; + } + let auth_client = self.auth_client.clone(); if self.auth_state.is_user_anonymous().unwrap_or_default() { diff --git a/app/src/context_chips/display_chip_tests.rs b/app/src/context_chips/display_chip_tests.rs index 39f28b9e1..1d4e6bf0e 100644 --- a/app/src/context_chips/display_chip_tests.rs +++ b/app/src/context_chips/display_chip_tests.rs @@ -79,7 +79,7 @@ fn test_format_git_branch_command_reports_missing_linked_worktree_path() { assert_eq!( format_git_branch_command(&value), - "echo 'Branch '\\''feature-a'\\'' is already checked out in another worktree, but Warp couldn'\\''t find its path.'" + "echo 'Branch '\\''feature-a'\\'' is already checked out in another worktree, but CastCodes couldn'\\''t find its path.'" ); } diff --git a/app/src/drive/import/import_tests.rs b/app/src/drive/import/import_tests.rs index 677c0cb15..65c60a75f 100644 --- a/app/src/drive/import/import_tests.rs +++ b/app/src/drive/import/import_tests.rs @@ -37,6 +37,6 @@ fn test_expand_directories() { .join("integration"); // Open a folder and verify we could expand it into the correct folder tree structure. - assert_eq!(warpui::r#async::block_on(expand_dirs([directory].into_iter().collect())).debug_print(), "(integration(tests(INTEGRATION_TESTING, data(test, test_launch_config, test_theme, test_theme_with_name, test_workflow))))"); + assert_eq!(warpui::r#async::block_on(expand_dirs([directory].into_iter().collect())).debug_print(), "(integration(tests(INTEGRATION_TESTING, data(test, test_launch_config, test_theme, test_theme_with_name, test_workflow, tweakcn_sample_dark, tweakcn_sample_light, ui_block_theme))))"); }); } diff --git a/app/src/lib.rs b/app/src/lib.rs index 314d211b1..778b7657a 100644 --- a/app/src/lib.rs +++ b/app/src/lib.rs @@ -821,6 +821,10 @@ mod launch_mode_tests { use super::*; use std::path::Path; + // The bundle-detection branch this exercises is macOS-only + // (`is_macos_app_bundle_executable`); on Linux and Windows the stub + // returns false and `should_run_as_cli` falls through. + #[cfg(target_os = "macos")] #[test] fn castcodes_app_bundle_executable_launches_gui() { assert!(!should_run_as_cli( diff --git a/app/src/pane_group/mod_tests.rs b/app/src/pane_group/mod_tests.rs index b6d42ef8b..7c2ae07c1 100644 --- a/app/src/pane_group/mod_tests.rs +++ b/app/src/pane_group/mod_tests.rs @@ -1110,6 +1110,13 @@ fn test_number_of_shared_panes() { #[test] fn test_start_shared_session_from_modal() { let _guard = FeatureFlag::CreatingSharedSessions.override_enabled(true); + // `open_share_session_modal` early-returns on channels without cloud + // services (e.g. the default OSS channel used for unit-test builds). This + // test exercises the hosted-channel path, so override the channel for its + // duration. + let _channel_guard = warp_core::channel::ChannelState::override_channel_for_test( + warp_core::channel::Channel::Stable, + ); App::test((), |mut app| async move { initialize_app(&mut app); let pane_group = mock_pane_group(&mut app, Default::default()); diff --git a/app/src/settings_view/environments_page_tests.rs b/app/src/settings_view/environments_page_tests.rs index 7ef48698d..f6f3c3466 100644 --- a/app/src/settings_view/environments_page_tests.rs +++ b/app/src/settings_view/environments_page_tests.rs @@ -688,7 +688,7 @@ fn test_render_list_page_with_personal_and_team_environments_shows_section_heade app.update(|ctx| { // Ensure UserWorkspaces has a current team name so the "Team" section renders with the - // shared header copy ("Shared by Warp and "). + // shared header copy ("Shared by CastCodes and "). UserWorkspaces::handle(ctx).update(ctx, |user_workspaces, ctx| { user_workspaces.setup_test_workspace(ctx); user_workspaces.update_current_workspace( @@ -754,7 +754,7 @@ fn test_render_list_page_with_personal_and_team_environments_shows_section_heade "Expected 'Personal' section header in rendered content: {text_content}" ); assert!( - text_content.contains("SHARED BY WARP AND KATARINA'S TEAM"), + text_content.contains("SHARED BY CASTCODES AND KATARINA'S TEAM"), "Expected shared section header in rendered content: {text_content}" ); }); diff --git a/crates/warp_core/src/channel/state.rs b/crates/warp_core/src/channel/state.rs index 8174c5231..50136cf79 100644 --- a/crates/warp_core/src/channel/state.rs +++ b/crates/warp_core/src/channel/state.rs @@ -343,6 +343,19 @@ impl ChannelState { *APP_VERSION.lock() = version; } + /// Temporarily overrides the active channel for the duration of a test. + /// Restores the previous channel when the returned guard is dropped, so + /// tests can exercise code paths gated on [`cloud_services_available`] + /// without permanently mutating global channel state. + #[cfg(feature = "test-util")] + #[must_use = "if unused the channel override will be immediately cleared"] + pub fn override_channel_for_test(channel: Channel) -> ChannelOverrideGuard { + let mut state = CHANNEL_STATE.lock(); + let previous = state.channel; + state.channel = channel; + ChannelOverrideGuard { previous } + } + #[cfg(not(feature = "test-util"))] pub fn app_version() -> Option<&'static str> { option_env!("GIT_RELEASE_TAG") @@ -403,6 +416,21 @@ impl ChannelState { } } +/// RAII guard returned by [`ChannelState::override_channel_for_test`]. Restores +/// the previous channel when dropped. +#[cfg(feature = "test-util")] +#[must_use = "if unused the channel override will be immediately cleared"] +pub struct ChannelOverrideGuard { + previous: Channel, +} + +#[cfg(feature = "test-util")] +impl Drop for ChannelOverrideGuard { + fn drop(&mut self) { + CHANNEL_STATE.lock().channel = self.previous; + } +} + /// Derives an HTTP(S) origin URL from a WebSocket URL by rewriting the scheme /// (`wss`→`https`, `ws`→`http`) and stripping the path, query, and fragment. /// Returns [`None`] when the input cannot be parsed as a URL or uses a scheme