use assert_cmd::Command;
use predicates::prelude::*;
use serde_json::Value;
use tempfile::TempDir;
fn write(path: &std::path::Path, contents: &str) {
std::fs::create_dir_all(path.parent().unwrap()).expect("mkdir");
std::fs::write(path, contents).expect("write");
}
fn create_claude_data_dir() -> TempDir {
let temp = TempDir::new().expect("temp");
let root = temp.path();
let slug = "-Users-test-Developer-app";
write(
&root.join("projects").join(slug).join("sess-both.jsonl"),
&[
r#"{"type":"user","content":"hi","timestamp":"2026-06-01T10:00:00.000Z","cwd":"/Users/test/Developer/app"}"#,
r#"{"type":"ai-title","aiTitle":"vision simulator session","sessionId":"sess-both"}"#,
]
.join("\n"),
);
write(
&root.join("projects").join(slug).join("sess-one.jsonl"),
&[
r#"{"type":"user","content":"hi","timestamp":"2026-05-01T10:00:00.000Z","cwd":"/Users/test/Developer/app"}"#,
r#"{"type":"ai-title","aiTitle":"vision notes only","sessionId":"sess-one"}"#,
]
.join("\n"),
);
write(
&root.join("history.jsonl"),
&[
r#"{"display":"apple vision pro simulator boot sequence","timestamp":1754402102000,"project":"/Users/test/Developer/app"}"#,
r#"{"display":"vision notes only, nothing else","timestamp":1754402100000,"project":"/Users/test/Developer/app"}"#,
]
.join("\n"),
);
temp
}
fn create_codex_data_dir() -> TempDir {
let temp = TempDir::new().expect("temp");
write(
&temp
.path()
.join("sessions/2026/07/27/rollout-codex-recall.jsonl"),
concat!(
"{\"timestamp\":\"2026-07-27T10:00:00Z\",\"type\":\"session_meta\",\"payload\":{\"id\":\"codex-recall\",\"cwd\":\"/Users/test/Developer/app\",\"thread_source\":\"user\"}}\n",
"{\"timestamp\":\"2026-07-27T10:00:01Z\",\"type\":\"response_item\",\"payload\":{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"debug auth callback Authorization: Bearer abc.def.ghi\"}]}}\n"
),
);
temp
}
#[test]
fn recall_surfaces_matching_fixture() {
let data = create_claude_data_dir();
let codex_home = TempDir::new().expect("codex");
Command::new(env!("CARGO_BIN_EXE_devsql"))
.env("CODEX_HOME", codex_home.path())
.args([
"recall",
"vision",
"--data-dir",
data.path().to_str().unwrap(),
"--format",
"json",
])
.assert()
.success()
.stdout(predicate::str::contains("vision simulator session"))
.stdout(predicate::str::contains("apple vision pro simulator boot"));
}
#[test]
fn recall_ranks_more_matches_first() {
let data = create_claude_data_dir();
let codex_home = TempDir::new().expect("codex");
let output = Command::new(env!("CARGO_BIN_EXE_devsql"))
.env("CODEX_HOME", codex_home.path())
.args([
"recall",
"vision simulator",
"--data-dir",
data.path().to_str().unwrap(),
"--format",
"json",
])
.assert()
.success()
.get_output()
.stdout
.clone();
let parsed: Value = serde_json::from_slice(&output).expect("valid json");
let prompts = parsed["prompts"].as_array().expect("prompts array");
assert_eq!(prompts.len(), 2, "both prompts match at least one term");
assert_eq!(prompts[0]["score"], serde_json::json!(2));
assert!(prompts[0]["prompt"]
.as_str()
.unwrap()
.contains("simulator boot"));
assert_eq!(prompts[1]["score"], serde_json::json!(1));
let sessions = parsed["sessions"].as_array().expect("sessions array");
assert_eq!(sessions[0]["score"], serde_json::json!(2));
assert_eq!(
sessions[0]["title"],
serde_json::json!("vision simulator session")
);
}
#[test]
fn recall_empty_terms_exit_zero() {
let data = create_claude_data_dir();
let codex_home = TempDir::new().expect("codex");
Command::new(env!("CARGO_BIN_EXE_devsql"))
.env("CODEX_HOME", codex_home.path())
.args([
"recall",
"a",
"--data-dir",
data.path().to_str().unwrap(),
"--format",
"json",
])
.assert()
.success()
.stdout(predicate::str::contains("\"total\": 0"));
}
#[test]
fn recall_returns_ranked_redacted_codex_threads() {
let claude = TempDir::new().expect("claude");
let codex_home = create_codex_data_dir();
let output = Command::new(env!("CARGO_BIN_EXE_devsql"))
.env("CODEX_HOME", codex_home.path())
.args([
"recall",
"auth callback",
"--data-dir",
claude.path().to_str().unwrap(),
"--format",
"json",
])
.assert()
.success()
.get_output()
.stdout
.clone();
let parsed: Value = serde_json::from_slice(&output).expect("valid json");
let threads = parsed["codex_threads"]
.as_array()
.expect("codex_threads array");
assert_eq!(threads.len(), 1);
assert_eq!(threads[0]["thread_id"], serde_json::json!("codex-recall"));
assert_eq!(threads[0]["score"], serde_json::json!(2));
let excerpt = threads[0]["excerpt"].as_str().expect("excerpt");
assert!(excerpt.contains("debug auth callback"));
assert!(excerpt.contains("Bearer <redacted>"));
assert!(!excerpt.contains("abc.def.ghi"));
}