use super::*;
use crate::config::{CloudStateConfig, GitAuthStateMethod, GitRemoteStateConfig};
#[must_use]
fn create_cloud_enabled_state() -> PipelineState {
let cloud = CloudStateConfig {
enabled: true,
api_url: Some("https://api.test.com".to_string()),
run_id: Some("run_123".to_string()),
heartbeat_interval_secs: 30,
graceful_degradation: true,
git_remote: GitRemoteStateConfig {
auth_method: GitAuthStateMethod::SshKey { key_path: None },
push_branch: "main".to_string(),
create_pr: false,
pr_title_template: None,
pr_body_template: None,
pr_base_branch: None,
force_push: false,
remote_name: "origin".to_string(),
},
};
let mut state = create_test_state();
state.cloud = cloud;
state
}
#[test]
fn test_cloud_disabled_no_push_effects() {
let mut state = create_test_state();
state.cloud = CloudStateConfig::disabled();
state.pending_push_commit = Some("abc123".to_string());
state.git_auth_configured = false;
let effect = determine_next_effect(&state);
assert!(
!matches!(effect, Effect::ConfigureGitAuth { .. }),
"Cloud disabled should not emit ConfigureGitAuth"
);
assert!(
!matches!(effect, Effect::PushToRemote { .. }),
"Cloud disabled should not emit PushToRemote"
);
}
#[test]
fn test_cloud_enabled_pending_push_configures_auth_first() {
let mut state = create_cloud_enabled_state();
state.pending_push_commit = Some("abc123".to_string());
state.git_auth_configured = false;
let effect = determine_next_effect(&state);
match effect {
Effect::ConfigureGitAuth { auth_method } => {
assert!(
auth_method.starts_with("ssh-key:"),
"Should configure SSH auth"
);
}
other => panic!("Expected ConfigureGitAuth, got: {other:?}"),
}
}
#[test]
fn test_cloud_enabled_auth_configured_emits_push() {
let mut state = create_cloud_enabled_state();
state.pending_push_commit = Some("abc123".to_string());
state.git_auth_configured = true;
let effect = determine_next_effect(&state);
match effect {
Effect::PushToRemote {
remote,
branch,
force,
commit_sha,
} => {
assert_eq!(remote, "origin", "Should push to origin");
assert_eq!(branch, "main", "Should push to main");
assert!(!force, "Force push should be disabled by default");
assert_eq!(commit_sha, "abc123", "Should push the pending commit");
}
other => panic!("Expected PushToRemote, got: {other:?}"),
}
}
#[test]
fn test_cloud_enabled_no_pending_push_no_effects() {
let mut state = create_cloud_enabled_state();
state.pending_push_commit = None;
state.git_auth_configured = true;
state.phase = PipelinePhase::Development;
let effect = determine_next_effect(&state);
assert!(
!matches!(effect, Effect::ConfigureGitAuth { .. }),
"No ConfigureGitAuth when nothing pending"
);
assert!(
!matches!(effect, Effect::PushToRemote { .. }),
"No PushToRemote when nothing pending"
);
}
#[test]
fn test_cloud_enabled_create_pr_in_finalizing() {
let mut state = create_cloud_enabled_state();
state.cloud.git_remote.create_pr = true;
state.cloud.git_remote.pr_base_branch = Some("main".to_string());
state.phase = PipelinePhase::Finalizing;
state.pr_created = false;
state.pending_push_commit = None; state.metrics.commits_created_total = 1;
state.push_count = 1;
state.last_pushed_commit = Some("abc123".to_string());
let effect = determine_next_effect(&state);
match effect {
Effect::CreatePullRequest {
base_branch,
head_branch,
title,
body: _,
} => {
assert_eq!(base_branch, "main", "Should target main branch");
assert_eq!(head_branch, "main", "Should use push branch");
assert!(
title.contains("Ralph workflow"),
"Should have default title"
);
}
other => panic!("Expected CreatePullRequest, got: {other:?}"),
}
}
#[test]
fn test_cloud_enabled_create_pr_renders_title_and_body_templates() {
let mut state = create_cloud_enabled_state();
state.cloud.git_remote.create_pr = true;
state.cloud.git_remote.pr_base_branch = Some("main".to_string());
state.cloud.git_remote.pr_title_template = Some("Ralph changes for {run_id}".to_string());
state.cloud.git_remote.pr_body_template = Some("Summary: {prompt_summary}".to_string());
state.phase = PipelinePhase::Finalizing;
state.pr_created = false;
state.pending_push_commit = None;
state.metrics.commits_created_total = 1;
state.push_count = 1;
state.last_pushed_commit = Some("abc123".to_string());
let effect = determine_next_effect(&state);
match effect {
Effect::CreatePullRequest { title, body, .. } => {
assert_eq!(title, "Ralph changes for run_123");
assert_eq!(body, "Summary: Ralph workflow run run_123");
}
other => panic!("Expected CreatePullRequest, got: {other:?}"),
}
}
#[test]
fn test_cloud_enabled_pr_already_created_no_effect() {
let mut state = create_cloud_enabled_state();
state.cloud.git_remote.create_pr = true;
state.phase = PipelinePhase::Finalizing;
state.pr_created = true;
state.pending_push_commit = None;
let effect = determine_next_effect(&state);
assert!(
!matches!(effect, Effect::CreatePullRequest { .. }),
"Should not create PR twice"
);
}
#[test]
fn test_cloud_enabled_create_pr_is_blocked_when_commits_failed_to_push() {
let mut state = create_cloud_enabled_state();
state.cloud.git_remote.create_pr = true;
state.phase = PipelinePhase::Finalizing;
state.pr_created = false;
state.pending_push_commit = None;
state.unpushed_commits = vec!["deadbeef".to_string()];
state.metrics.commits_created_total = 1;
state.push_count = 0;
state.last_pushed_commit = None;
let effect = determine_next_effect(&state);
match effect {
Effect::EmitCompletionMarkerAndTerminate { is_failure, reason } => {
assert!(
is_failure,
"Should terminate as failure when PR is impossible"
);
let reason = reason.unwrap_or_default();
assert!(
reason.to_ascii_lowercase().contains("push"),
"Reason should mention push requirement: {reason}"
);
}
other => panic!(
"Expected EmitCompletionMarkerAndTerminate when unpushed commits exist, got: {other:?}"
),
}
}
#[test]
fn test_cloud_enabled_token_auth_format() {
let mut state = create_cloud_enabled_state();
state.cloud.git_remote.auth_method = GitAuthStateMethod::Token {
username: "oauth2".to_string(),
};
state.pending_push_commit = Some("abc123".to_string());
state.git_auth_configured = false;
let effect = determine_next_effect(&state);
match effect {
Effect::ConfigureGitAuth { auth_method } => {
assert_eq!(auth_method, "token:oauth2", "Should format token auth");
}
other => panic!("Expected ConfigureGitAuth, got: {other:?}"),
}
}
#[test]
fn test_cloud_enabled_credential_helper_format() {
let mut state = create_cloud_enabled_state();
state.cloud.git_remote.auth_method = GitAuthStateMethod::CredentialHelper {
helper: "gcloud".to_string(),
};
state.pending_push_commit = Some("abc123".to_string());
state.git_auth_configured = false;
let effect = determine_next_effect(&state);
match effect {
Effect::ConfigureGitAuth { auth_method } => {
assert_eq!(
auth_method, "credential-helper:gcloud",
"Should format credential helper"
);
}
other => panic!("Expected ConfigureGitAuth, got: {other:?}"),
}
}
#[test]
fn test_cloud_enabled_force_push_when_configured() {
let mut state = create_cloud_enabled_state();
state.cloud.git_remote.force_push = true;
state.pending_push_commit = Some("abc123".to_string());
state.git_auth_configured = true;
let effect = determine_next_effect(&state);
match effect {
Effect::PushToRemote { force, .. } => {
assert!(force, "Force push should be enabled when configured");
}
other => panic!("Expected PushToRemote, got: {other:?}"),
}
}
#[test]
fn test_cloud_push_priority_over_phase_effects() {
let mut state = create_cloud_enabled_state();
state.phase = PipelinePhase::Development;
state.pending_push_commit = Some("abc123".to_string());
state.git_auth_configured = true;
let effect = determine_next_effect(&state);
assert!(
matches!(effect, Effect::PushToRemote { .. }),
"Push should have priority over phase effects"
);
}
#[test]
fn test_cloud_push_does_not_block_other_priorities() {
let mut state = create_cloud_enabled_state();
state.pending_push_commit = Some("abc123".to_string());
state.git_auth_configured = true;
state.phase = PipelinePhase::Development;
state.agent_chain.current_role = AgentRole::Analysis;
state.continuation.xsd_retry_pending = true;
state.continuation.xsd_retry_count = 0;
state.continuation.max_xsd_retry_count = 3;
let effect = determine_next_effect(&state);
assert!(
matches!(
effect,
Effect::InvokeAnalysisAgent { .. } | Effect::InitializeAgentChain { .. }
),
"XSD retry effects should take precedence over cloud push, got: {effect:?}"
);
}
#[test]
fn test_cloud_pr_only_in_finalizing_phase() {
let mut state = create_cloud_enabled_state();
state.cloud.git_remote.create_pr = true;
state.phase = PipelinePhase::Development; state.pr_created = false;
state.pending_push_commit = None;
let effect = determine_next_effect(&state);
assert!(
!matches!(effect, Effect::CreatePullRequest { .. }),
"PR should only be created in Finalizing phase"
);
}