mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-17 15:34:54 +00:00
test: reintroduce minimal tests
This commit is contained in:
@@ -2437,6 +2437,17 @@ impl Session {
|
||||
&compacted.message,
|
||||
);
|
||||
}
|
||||
RolloutItem::Event(recorded_event) => {
|
||||
if let code_protocol::protocol::EventMsg::UserMessage(user_msg_event) = &recorded_event.msg {
|
||||
history.push(ResponseItem::Message {
|
||||
id: Some(recorded_event.id.clone()),
|
||||
role: "user".to_string(),
|
||||
content: vec![ContentItem::InputText {
|
||||
text: user_msg_event.message.clone(),
|
||||
}],
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use code_protocol::ConversationId;
|
||||
use code_protocol::models::{ContentItem, ResponseItem};
|
||||
use code_protocol::protocol::EventMsg as ProtoEventMsg;
|
||||
use code_protocol::protocol::SessionSource;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
@@ -325,7 +327,27 @@ impl RolloutRecorder {
|
||||
items.push(RolloutItem::ResponseItem(item));
|
||||
}
|
||||
RolloutItem::Event(ev) => {
|
||||
items.push(RolloutItem::Event(ev));
|
||||
match &ev.msg {
|
||||
ProtoEventMsg::UserMessage(user_msg) => {
|
||||
items.push(RolloutItem::ResponseItem(ResponseItem::Message {
|
||||
id: Some(ev.id.clone()),
|
||||
role: "user".to_string(),
|
||||
content: vec![ContentItem::InputText {
|
||||
text: user_msg.message.clone(),
|
||||
}],
|
||||
}));
|
||||
}
|
||||
ProtoEventMsg::AgentMessage(agent_msg) => {
|
||||
items.push(RolloutItem::ResponseItem(ResponseItem::Message {
|
||||
id: Some(ev.id.clone()),
|
||||
role: "assistant".to_string(),
|
||||
content: vec![ContentItem::OutputText {
|
||||
text: agent_msg.message.clone(),
|
||||
}],
|
||||
}));
|
||||
}
|
||||
_ => items.push(RolloutItem::Event(ev)),
|
||||
}
|
||||
}
|
||||
RolloutItem::Compacted(compacted) => {
|
||||
items.push(RolloutItem::Compacted(compacted));
|
||||
|
||||
@@ -13,11 +13,13 @@ use time::format_description::FormatItem;
|
||||
use time::macros::format_description;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::config::{Config, ConfigOverrides, ConfigToml};
|
||||
use crate::rollout::INTERACTIVE_SESSION_SOURCES;
|
||||
use crate::rollout::list::ConversationsPage;
|
||||
use crate::rollout::list::Cursor;
|
||||
use crate::rollout::list::get_conversation;
|
||||
use crate::rollout::list::get_conversations;
|
||||
use code_protocol::models::{ContentItem, ResponseItem};
|
||||
use code_protocol::ConversationId;
|
||||
use code_protocol::protocol::{
|
||||
EventMsg as ProtoEventMsg,
|
||||
@@ -32,6 +34,20 @@ use code_protocol::protocol::{
|
||||
|
||||
const NO_SOURCE_FILTER: &[SessionSource] = &[];
|
||||
|
||||
fn reconstruct_history_like_rollout(items: &[RolloutItem]) -> Vec<ResponseItem> {
|
||||
let mut history = Vec::new();
|
||||
for item in items {
|
||||
match item {
|
||||
RolloutItem::ResponseItem(response) => history.push(response.clone()),
|
||||
RolloutItem::Compacted(compacted) => {
|
||||
history.push(ResponseItem::from(compacted.clone()));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
history
|
||||
}
|
||||
|
||||
fn assert_page_summary(
|
||||
page: &ConversationsPage,
|
||||
expected_items: &[(PathBuf, &str)],
|
||||
@@ -142,6 +158,119 @@ fn write_session_file(
|
||||
Ok((dt, uuid))
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_resume_reconstruct_history_drops_user_events() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
let code_home = temp.path();
|
||||
let workspace = code_home.join("workspace");
|
||||
fs::create_dir_all(&workspace).unwrap();
|
||||
|
||||
let sessions_dir = code_home
|
||||
.join("sessions")
|
||||
.join("2025")
|
||||
.join("10")
|
||||
.join("06");
|
||||
fs::create_dir_all(&sessions_dir).unwrap();
|
||||
|
||||
let rollout_ts = "2025-10-06T09-00-00";
|
||||
let session_uuid = Uuid::from_u128(0xfeed_babe_u128);
|
||||
let rollout_path = sessions_dir.join(format!(
|
||||
"rollout-{rollout_ts}-{session_uuid}.jsonl"
|
||||
));
|
||||
|
||||
let mut writer = BufWriter::new(File::create(&rollout_path).unwrap());
|
||||
|
||||
let session_meta = SessionMeta {
|
||||
id: ConversationId::from(session_uuid),
|
||||
timestamp: "2025-10-06T09:00:00.000Z".to_string(),
|
||||
cwd: workspace.clone(),
|
||||
originator: "regression-test".to_string(),
|
||||
cli_version: "0.0.0-test".to_string(),
|
||||
instructions: Some("explain async/await".to_string()),
|
||||
source: SessionSource::Cli,
|
||||
};
|
||||
serde_json::to_writer(&mut writer, &RolloutLine {
|
||||
timestamp: session_meta.timestamp.clone(),
|
||||
item: RolloutItem::SessionMeta(SessionMetaLine {
|
||||
meta: session_meta,
|
||||
git: None,
|
||||
}),
|
||||
})
|
||||
.unwrap();
|
||||
writer.write_all(b"\n").unwrap();
|
||||
|
||||
let user_event = RecordedEvent {
|
||||
id: "evt-user-0".to_string(),
|
||||
event_seq: 0,
|
||||
order: None,
|
||||
msg: ProtoEventMsg::UserMessage(UserMessageEvent {
|
||||
message: "hi there".to_string(),
|
||||
kind: None,
|
||||
images: None,
|
||||
}),
|
||||
};
|
||||
serde_json::to_writer(&mut writer, &RolloutLine {
|
||||
timestamp: "2025-10-06T09:00:01.000Z".to_string(),
|
||||
item: RolloutItem::Event(user_event),
|
||||
})
|
||||
.unwrap();
|
||||
writer.write_all(b"\n").unwrap();
|
||||
|
||||
let assistant_response = ResponseItem::Message {
|
||||
id: Some("msg-assistant-0".to_string()),
|
||||
role: "assistant".to_string(),
|
||||
content: vec![ContentItem::OutputText {
|
||||
text: "Sure, let's talk about async/await.".to_string(),
|
||||
}],
|
||||
};
|
||||
serde_json::to_writer(&mut writer, &RolloutLine {
|
||||
timestamp: "2025-10-06T09:00:02.000Z".to_string(),
|
||||
item: RolloutItem::ResponseItem(assistant_response),
|
||||
})
|
||||
.unwrap();
|
||||
writer.write_all(b"\n").unwrap();
|
||||
writer.flush().unwrap();
|
||||
|
||||
let mut overrides = ConfigOverrides::default();
|
||||
overrides.cwd = Some(workspace.clone());
|
||||
let config = Config::load_from_base_config_with_overrides(
|
||||
ConfigToml::default(),
|
||||
overrides,
|
||||
code_home.to_path_buf(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let (recorder, saved) = crate::rollout::RolloutRecorder::resume(&config, &rollout_path)
|
||||
.await
|
||||
.expect("resume should succeed");
|
||||
recorder.shutdown().await.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
saved.items.len(),
|
||||
3,
|
||||
"expected to load session meta + user event + assistant response"
|
||||
);
|
||||
|
||||
let reconstructed = reconstruct_history_like_rollout(&saved.items);
|
||||
|
||||
assert_eq!(
|
||||
reconstructed.len(),
|
||||
2,
|
||||
"expected replay history to surface both user and assistant messages"
|
||||
);
|
||||
|
||||
let user_messages = reconstructed
|
||||
.iter()
|
||||
.filter(|item| matches!(item, ResponseItem::Message { role, .. } if role == "user"))
|
||||
.count();
|
||||
|
||||
assert_eq!(
|
||||
user_messages,
|
||||
1,
|
||||
"expected one user message to survive resume replay"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_list_conversations_latest_first() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
|
||||
@@ -263,6 +263,9 @@ fn parse_rollout_candidate(path: &Path, target_cwd: &Path) -> Option<ResumeCandi
|
||||
if let Some(snippet) = extract_user_snippet_from_event(&event) {
|
||||
last_user_snippet = Some(snippet);
|
||||
}
|
||||
if matches!(event, EventMsg::UserMessage(_)) {
|
||||
message_count = message_count.saturating_add(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
RolloutItem::Compacted(_) | RolloutItem::TurnContext(_) => {}
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
pub mod discovery;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used)]
|
||||
|
||||
use std::fs::{self, File};
|
||||
use std::io::{BufWriter, Write};
|
||||
use std::path::Path;
|
||||
|
||||
use tempfile::TempDir;
|
||||
use uuid::Uuid;
|
||||
|
||||
use code_protocol::ConversationId;
|
||||
use code_protocol::protocol::{
|
||||
EventMsg as ProtoEventMsg,
|
||||
RecordedEvent,
|
||||
RolloutItem,
|
||||
RolloutLine,
|
||||
SessionMeta,
|
||||
SessionMetaLine,
|
||||
SessionSource,
|
||||
UserMessageEvent,
|
||||
};
|
||||
|
||||
fn write_event_only_session(path: &Path, cwd: &Path) {
|
||||
let file = File::create(path).unwrap();
|
||||
let mut writer = BufWriter::new(file);
|
||||
|
||||
let session_meta = SessionMeta {
|
||||
id: ConversationId::from(Uuid::from_u128(0xDEAD_BEEF_u128)),
|
||||
timestamp: "2025-10-06T12:00:00.000Z".to_string(),
|
||||
cwd: cwd.to_path_buf(),
|
||||
originator: "resume-test".to_string(),
|
||||
cli_version: "0.0.0-test".to_string(),
|
||||
instructions: None,
|
||||
source: SessionSource::Cli,
|
||||
};
|
||||
|
||||
let meta_line = RolloutLine {
|
||||
timestamp: "2025-10-06T12:00:00.000Z".to_string(),
|
||||
item: RolloutItem::SessionMeta(SessionMetaLine {
|
||||
meta: session_meta,
|
||||
git: None,
|
||||
}),
|
||||
};
|
||||
serde_json::to_writer(&mut writer, &meta_line).unwrap();
|
||||
writer.write_all(b"\n").unwrap();
|
||||
|
||||
let user_event = RecordedEvent {
|
||||
id: "evt-user".to_string(),
|
||||
event_seq: 1,
|
||||
order: None,
|
||||
msg: ProtoEventMsg::UserMessage(UserMessageEvent {
|
||||
message: "restore me".to_string(),
|
||||
kind: None,
|
||||
images: None,
|
||||
}),
|
||||
};
|
||||
let event_line = RolloutLine {
|
||||
timestamp: "2025-10-06T12:00:01.000Z".to_string(),
|
||||
item: RolloutItem::Event(user_event),
|
||||
};
|
||||
serde_json::to_writer(&mut writer, &event_line).unwrap();
|
||||
writer.write_all(b"\n").unwrap();
|
||||
writer.flush().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn event_only_sessions_are_dropped_by_resume_discovery() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
let code_home = temp.path();
|
||||
let project_cwd = code_home.join("project");
|
||||
fs::create_dir_all(&project_cwd).unwrap();
|
||||
|
||||
let sessions_dir = code_home
|
||||
.join("sessions")
|
||||
.join("2025")
|
||||
.join("10")
|
||||
.join("06");
|
||||
fs::create_dir_all(&sessions_dir).unwrap();
|
||||
|
||||
let rollout_path = sessions_dir.join("rollout-2025-10-06T12-00-00-00000000-0000-0000-0000-000000000042.jsonl");
|
||||
write_event_only_session(&rollout_path, &project_cwd);
|
||||
|
||||
let results = super::discovery::list_sessions_for_cwd(&project_cwd, code_home);
|
||||
|
||||
assert!(
|
||||
!results.is_empty(),
|
||||
"expected resume discovery to surface sessions that only contain Event-based user messages. \
|
||||
An empty result demonstrates the regression where event-only transcripts are filtered out, \
|
||||
making '/resume' miss recent conversations."
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user