Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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| {
Expand Down
25 changes: 19 additions & 6 deletions app/src/ai/blocklist/handoff/touched_repos_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand All @@ -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"),
]
);
}

Expand All @@ -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"),
Expand Down Expand Up @@ -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"),
]
);
}
Expand Down
18 changes: 13 additions & 5 deletions app/src/auth/auth_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,18 +151,18 @@ impl AuthManager {
enforce_state_validation: bool,
ctx: &mut ModelContext<Self>,
) {
if Self::hosted_auth_disabled() {
log::info!("Ignoring auth redirect: hosted auth is disabled for this channel");
return;
}

let AuthRedirectPayload {
refresh_token,
user_uid,
deleted_anonymous_user,
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) {
Expand All @@ -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() {
Expand Down
2 changes: 1 addition & 1 deletion app/src/context_chips/display_chip_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.'"
);
}

Expand Down
2 changes: 1 addition & 1 deletion app/src/drive/import/import_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))))");
});
}
4 changes: 4 additions & 0 deletions app/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
7 changes: 7 additions & 0 deletions app/src/pane_group/mod_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
4 changes: 2 additions & 2 deletions app/src/settings_view/environments_page_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 <team>").
// shared header copy ("Shared by CastCodes and <team>").
UserWorkspaces::handle(ctx).update(ctx, |user_workspaces, ctx| {
user_workspaces.setup_test_workspace(ctx);
user_workspaces.update_current_workspace(
Expand Down Expand Up @@ -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}"
);
});
Expand Down
28 changes: 28 additions & 0 deletions crates/warp_core/src/channel/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand Down
Loading