use crate::prompts::{SubstitutionEntry, SubstitutionLog, SubstitutionSource};
use crate::reducer::event::{PipelineEvent, PipelinePhase, PromptInputEvent};
use crate::reducer::state::PipelineState;
use crate::reducer::state_reduction::reduce;
#[test]
fn test_reduce_template_rendered_complete_with_defaults() {
let state = PipelineState::initial(1, 0);
let log = SubstitutionLog {
template_name: "commit_message_xml".to_string(),
substituted: vec![
SubstitutionEntry {
name: "DIFF".to_string(),
source: SubstitutionSource::Value,
},
SubstitutionEntry {
name: "OPTIONAL".to_string(),
source: SubstitutionSource::Default,
},
],
unsubstituted: vec![],
};
let event = PipelineEvent::PromptInput(PromptInputEvent::TemplateRendered {
phase: PipelinePhase::CommitMessage,
template_name: "commit_message_xml".to_string(),
log,
});
let new_state = reduce(state, event);
assert!(
new_state.last_substitution_log.is_some(),
"Substitution log should be stored in state"
);
assert!(
!new_state.template_validation_failed,
"Complete substitution log should not mark validation as failed"
);
assert!(
new_state.template_validation_unsubstituted.is_empty(),
"No unsubstituted placeholders should be recorded for complete logs"
);
let stored_log = new_state.last_substitution_log.unwrap();
assert!(
stored_log.is_complete(),
"Stored log should be complete (no unsubstituted placeholders)"
);
assert_eq!(
stored_log.substituted.len(),
2,
"Should have 2 substituted entries"
);
assert_eq!(stored_log.substituted[0].name, "DIFF");
assert_eq!(stored_log.substituted[0].source, SubstitutionSource::Value);
assert_eq!(stored_log.substituted[1].name, "OPTIONAL");
assert_eq!(
stored_log.substituted[1].source,
SubstitutionSource::Default
);
assert!(
stored_log.unsubstituted.is_empty(),
"Should have no unsubstituted placeholders"
);
assert_eq!(stored_log.template_name, "commit_message_xml");
}
#[test]
fn test_reduce_template_rendered_incomplete() {
let state = PipelineState::initial(1, 0);
let log = SubstitutionLog {
template_name: "commit_message_xml".to_string(),
substituted: vec![SubstitutionEntry {
name: "A".to_string(),
source: SubstitutionSource::Value,
}],
unsubstituted: vec!["B".to_string(), "C".to_string()],
};
let event = PipelineEvent::PromptInput(PromptInputEvent::TemplateRendered {
phase: PipelinePhase::CommitMessage,
template_name: "commit_message_xml".to_string(),
log,
});
let new_state = reduce(state, event);
assert!(
new_state.last_substitution_log.is_some(),
"Substitution log should be stored in state even if incomplete"
);
assert!(
new_state.template_validation_failed,
"Incomplete substitution log should mark validation as failed"
);
assert_eq!(
new_state.template_validation_unsubstituted,
vec!["B".to_string(), "C".to_string()],
"Unsubstituted placeholders should be recorded for validation failures"
);
let stored_log = new_state.last_substitution_log.unwrap();
assert!(
!stored_log.is_complete(),
"Stored log should be incomplete (has unsubstituted placeholders)"
);
assert_eq!(
stored_log.unsubstituted.len(),
2,
"Should have 2 unsubstituted placeholders"
);
assert_eq!(stored_log.unsubstituted[0], "B");
assert_eq!(stored_log.unsubstituted[1], "C");
}
#[test]
fn test_reduce_template_rendered_empty_with_default() {
let state = PipelineState::initial(1, 0);
let log = SubstitutionLog {
template_name: "test_template".to_string(),
substituted: vec![
SubstitutionEntry {
name: "NAME".to_string(),
source: SubstitutionSource::EmptyWithDefault,
},
SubstitutionEntry {
name: "TITLE".to_string(),
source: SubstitutionSource::Value,
},
],
unsubstituted: vec![],
};
let event = PipelineEvent::PromptInput(PromptInputEvent::TemplateRendered {
phase: PipelinePhase::CommitMessage,
template_name: "commit_message_xml".to_string(),
log,
});
let new_state = reduce(state, event);
let stored_log = new_state
.last_substitution_log
.expect("Log should be stored");
assert!(
stored_log.is_complete(),
"Log with EmptyWithDefault should be complete"
);
let defaults = stored_log.defaults_used();
assert_eq!(defaults.len(), 1, "Should have 1 default used");
assert!(
defaults.contains(&"NAME"),
"NAME should be in defaults_used (EmptyWithDefault)"
);
}
#[test]
fn test_reduce_template_rendered_log_persists_across_state() {
let state = PipelineState::initial(1, 0);
let log = SubstitutionLog {
template_name: "first_template".to_string(),
substituted: vec![SubstitutionEntry {
name: "VAR1".to_string(),
source: SubstitutionSource::Value,
}],
unsubstituted: vec![],
};
let event1 = PipelineEvent::PromptInput(PromptInputEvent::TemplateRendered {
phase: PipelinePhase::Planning,
template_name: "first_template".to_string(),
log,
});
let state_after_first = reduce(state, event1);
assert!(state_after_first.last_substitution_log.is_some());
assert_eq!(
state_after_first
.last_substitution_log
.as_ref()
.unwrap()
.template_name,
"first_template"
);
let log2 = SubstitutionLog {
template_name: "second_template".to_string(),
substituted: vec![SubstitutionEntry {
name: "VAR2".to_string(),
source: SubstitutionSource::Default,
}],
unsubstituted: vec![],
};
let event2 = PipelineEvent::PromptInput(PromptInputEvent::TemplateRendered {
phase: PipelinePhase::Development,
template_name: "second_template".to_string(),
log: log2,
});
let state_after_second = reduce(state_after_first, event2);
assert!(state_after_second.last_substitution_log.is_some());
assert_eq!(
state_after_second
.last_substitution_log
.as_ref()
.unwrap()
.template_name,
"second_template"
);
}
#[test]
fn test_reduce_template_rendered_different_phases() {
let phases_to_test = vec![
PipelinePhase::Planning,
PipelinePhase::Development,
PipelinePhase::Review,
PipelinePhase::CommitMessage,
];
for phase in phases_to_test {
let state = PipelineState::initial(1, 0);
let log = SubstitutionLog {
template_name: format!("{phase:?}_template"),
substituted: vec![SubstitutionEntry {
name: "TEST_VAR".to_string(),
source: SubstitutionSource::Value,
}],
unsubstituted: vec![],
};
let event = PipelineEvent::PromptInput(PromptInputEvent::TemplateRendered {
phase,
template_name: format!("{phase:?}_template"),
log: log.clone(),
});
let new_state = reduce(state, event);
assert!(
new_state.last_substitution_log.is_some(),
"Log should be stored for phase {phase:?}"
);
assert_eq!(
new_state.last_substitution_log.unwrap().template_name,
format!("{phase:?}_template")
);
}
}
#[test]
fn test_reduce_template_rendered_with_multiple_defaults() {
let state = PipelineState::initial(1, 0);
let log = SubstitutionLog {
template_name: "multi_default_template".to_string(),
substituted: vec![
SubstitutionEntry {
name: "A".to_string(),
source: SubstitutionSource::Value,
},
SubstitutionEntry {
name: "B".to_string(),
source: SubstitutionSource::Default,
},
SubstitutionEntry {
name: "C".to_string(),
source: SubstitutionSource::EmptyWithDefault,
},
SubstitutionEntry {
name: "D".to_string(),
source: SubstitutionSource::Default,
},
],
unsubstituted: vec![],
};
let event = PipelineEvent::PromptInput(PromptInputEvent::TemplateRendered {
phase: PipelinePhase::Development,
template_name: "multi_default_template".to_string(),
log,
});
let new_state = reduce(state, event);
let stored_log = new_state
.last_substitution_log
.expect("Log should be stored");
assert_eq!(stored_log.substituted.len(), 4);
let defaults = stored_log.defaults_used();
assert_eq!(defaults.len(), 3, "Should have 3 defaults used");
assert!(defaults.contains(&"B"));
assert!(defaults.contains(&"C"));
assert!(defaults.contains(&"D"));
assert!(!defaults.contains(&"A"));
}