use osp_cli::App;
use osp_cli::app::BufferedUiSink;
use crate::app::support::parse_json_output;
fn debug_complete_payload(line: &str, steps: &[&str]) -> serde_json::Value {
let app = App::builder().build();
let mut sink = BufferedUiSink::default();
let mut args = vec![
"osp".to_string(),
"--json".to_string(),
"--defaults-only".to_string(),
"repl".to_string(),
"debug-complete".to_string(),
"--line".to_string(),
line.to_string(),
];
for step in steps {
args.push("--step".to_string());
args.push((*step).to_string());
}
let exit = app
.run_with_sink(args.iter().map(String::as_str), &mut sink)
.expect("debug-complete should run");
assert_eq!(exit, 0);
assert!(sink.stderr.is_empty(), "unexpected stderr: {}", sink.stderr);
let arg_refs = args.iter().map(String::as_str).collect::<Vec<_>>();
parse_json_output(
"repl_completion_flow/debug_complete",
&arg_refs,
&sink.stdout,
&sink.stderr,
)
}
fn match_labels(payload: &serde_json::Value) -> Vec<String> {
payload["matches"]
.as_array()
.expect("matches should render as an array")
.iter()
.map(|item| {
item["label"]
.as_str()
.expect("match label should be a string")
.to_string()
})
.collect()
}
#[test]
fn repl_completion_flow_tracks_committed_scope_and_consumed_flags() {
let root_stub = match_labels(&debug_complete_payload("config", &[]));
assert!(root_stub.contains(&"config".to_string()));
assert!(!root_stub.contains(&"show".to_string()));
let scoped = match_labels(&debug_complete_payload("config ", &[]));
assert!(scoped.contains(&"show".to_string()));
assert!(scoped.contains(&"get".to_string()));
assert!(scoped.contains(&"explain".to_string()));
assert!(!scoped.contains(&"config".to_string()));
let uncommitted_subcommand = match_labels(&debug_complete_payload("config show", &[]));
assert!(uncommitted_subcommand.contains(&"show".to_string()));
assert!(!uncommitted_subcommand.contains(&"--raw".to_string()));
let flag_slot = match_labels(&debug_complete_payload("config show ", &[]));
assert!(flag_slot.contains(&"--raw".to_string()));
assert!(flag_slot.contains(&"--sources".to_string()));
assert!(!flag_slot.contains(&"show".to_string()));
let uncommitted_flag = match_labels(&debug_complete_payload("config show --raw", &[]));
assert!(uncommitted_flag.contains(&"--raw".to_string()));
assert!(uncommitted_flag.contains(&"--sources".to_string()));
let committed_flag = match_labels(&debug_complete_payload("config show --raw ", &[]));
assert!(!committed_flag.contains(&"--raw".to_string()));
assert!(committed_flag.contains(&"--sources".to_string()));
}
#[test]
fn repl_completion_flow_first_tab_returns_matches_for_current_slot() {
let payload = debug_complete_payload("config ", &["tab"]);
let frames = payload
.as_array()
.expect("stepped debug-complete should render frames");
assert_eq!(frames.len(), 1);
assert_eq!(frames[0]["step"], "tab");
assert!(
frames[0]["state"]["matches"]
.as_array()
.expect("frame matches should render as an array")
.iter()
.any(|item| item["label"] == "show")
);
}