Add operator Mentra memory browser
This commit is contained in:
175
vendor/mentra/tests/branching.rs
vendored
Normal file
175
vendor/mentra/tests/branching.rs
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
//! Public-API tests for the transcript entry tree.
|
||||
//!
|
||||
//! Branching is what lets a conversation return to an earlier point and
|
||||
//! continue differently — "undo that exchange and try another instruction",
|
||||
//! editing a message and re-running, or exploring two approaches from a shared
|
||||
//! prefix — without starting a new session and replaying a prefix by hand.
|
||||
|
||||
use mentra::{
|
||||
AgentTranscript, ContentBlock, Message,
|
||||
transcript::{EntryId, TranscriptItem},
|
||||
};
|
||||
|
||||
fn user(text: &str) -> TranscriptItem {
|
||||
TranscriptItem::user_turn(Message::user(ContentBlock::text(text)))
|
||||
}
|
||||
|
||||
fn assistant(text: &str) -> TranscriptItem {
|
||||
TranscriptItem::assistant_turn(Message::assistant(ContentBlock::text(text)))
|
||||
}
|
||||
|
||||
fn transcript_of(texts: &[&str]) -> AgentTranscript {
|
||||
let mut transcript = AgentTranscript::default();
|
||||
for (index, text) in texts.iter().enumerate() {
|
||||
if index % 2 == 0 {
|
||||
transcript.push(user(text));
|
||||
} else {
|
||||
transcript.push(assistant(text));
|
||||
}
|
||||
}
|
||||
transcript
|
||||
}
|
||||
|
||||
fn texts(transcript: &AgentTranscript) -> Vec<String> {
|
||||
transcript
|
||||
.items()
|
||||
.iter()
|
||||
.map(TranscriptItem::text)
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn appending_hangs_each_entry_from_the_leaf() {
|
||||
let transcript = transcript_of(&["one", "two", "three"]);
|
||||
let items = transcript.items();
|
||||
|
||||
assert_eq!(items[0].parent_id, None, "the first entry is a root");
|
||||
assert_eq!(items[1].parent_id.as_ref(), Some(&items[0].id));
|
||||
assert_eq!(items[2].parent_id.as_ref(), Some(&items[1].id));
|
||||
assert_eq!(transcript.leaf(), Some(&items[2].id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn branching_rewinds_the_active_path_without_deleting_anything() {
|
||||
let mut transcript = transcript_of(&["ask", "answer", "follow up"]);
|
||||
let rewind_to = transcript.items()[0].id.clone();
|
||||
|
||||
let moved = transcript
|
||||
.branch_from(&rewind_to)
|
||||
.expect("entry is on path");
|
||||
|
||||
assert_eq!(moved, 2, "two entries leave the active path");
|
||||
assert_eq!(texts(&transcript), vec!["ask"]);
|
||||
assert_eq!(transcript.leaf(), Some(&rewind_to));
|
||||
assert_eq!(
|
||||
transcript.archived().len(),
|
||||
2,
|
||||
"abandoned entries stay in the transcript"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_new_turn_after_branching_becomes_a_sibling() {
|
||||
let mut transcript = transcript_of(&["ask", "first answer"]);
|
||||
let fork_point = transcript.items()[0].id.clone();
|
||||
|
||||
transcript.branch_from(&fork_point).expect("on path");
|
||||
transcript.push(assistant("second answer"));
|
||||
|
||||
let children = transcript.children(&fork_point);
|
||||
assert_eq!(children.len(), 2, "the fork point now has two paths");
|
||||
|
||||
let child_texts: Vec<String> = children.iter().map(|item| item.text()).collect();
|
||||
assert!(child_texts.contains(&"first answer".to_string()));
|
||||
assert!(child_texts.contains(&"second answer".to_string()));
|
||||
|
||||
// Only the new path is live.
|
||||
assert_eq!(texts(&transcript), vec!["ask", "second answer"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_abandoned_branch_can_be_returned_to() {
|
||||
let mut transcript = transcript_of(&["ask", "first answer"]);
|
||||
let fork_point = transcript.items()[0].id.clone();
|
||||
let first_answer = transcript.items()[1].id.clone();
|
||||
|
||||
transcript.branch_from(&fork_point).expect("on path");
|
||||
transcript.push(assistant("second answer"));
|
||||
|
||||
// The abandoned entry is still addressable, which is what makes this a
|
||||
// branch rather than a truncation.
|
||||
let recovered = transcript.entry(&first_answer).expect("still present");
|
||||
assert_eq!(recovered.text(), "first answer");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn branching_to_an_unknown_entry_is_refused() {
|
||||
let mut transcript = transcript_of(&["ask"]);
|
||||
|
||||
let error = transcript
|
||||
.branch_from(&EntryId::new())
|
||||
.expect_err("an entry that was never appended is not a branch point");
|
||||
|
||||
assert!(error.to_string().contains("no entry"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn branching_to_the_leaf_changes_nothing() {
|
||||
let mut transcript = transcript_of(&["ask", "answer"]);
|
||||
let leaf = transcript.leaf().cloned().expect("a leaf");
|
||||
|
||||
let moved = transcript.branch_from(&leaf).expect("the leaf is on path");
|
||||
|
||||
assert_eq!(moved, 0);
|
||||
assert_eq!(texts(&transcript), vec!["ask", "answer"]);
|
||||
assert!(transcript.archived().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_tree_survives_a_round_trip() {
|
||||
let mut transcript = transcript_of(&["ask", "first answer"]);
|
||||
let fork_point = transcript.items()[0].id.clone();
|
||||
transcript.branch_from(&fork_point).expect("on path");
|
||||
transcript.push(assistant("second answer"));
|
||||
|
||||
let encoded = serde_json::to_string(&transcript).expect("serializes");
|
||||
let decoded: AgentTranscript = serde_json::from_str(&encoded).expect("deserializes");
|
||||
|
||||
assert_eq!(decoded, transcript);
|
||||
assert_eq!(
|
||||
decoded.children(&fork_point).len(),
|
||||
2,
|
||||
"both paths survive persistence"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_transcript_written_before_entries_had_ids_still_loads_linked() {
|
||||
// Build the shape mentra persisted previously by stripping the tree
|
||||
// fields back out of a current transcript, so the fixture cannot drift
|
||||
// from the real serialization format.
|
||||
let modern = transcript_of(&["ask", "answer"]);
|
||||
let mut encoded: serde_json::Value = serde_json::to_value(&modern).expect("serializes");
|
||||
for item in encoded["items"]
|
||||
.as_array_mut()
|
||||
.expect("items is an array")
|
||||
.iter_mut()
|
||||
{
|
||||
let object = item.as_object_mut().expect("each item is an object");
|
||||
object.remove("id");
|
||||
object.remove("parent_id");
|
||||
}
|
||||
|
||||
let transcript: AgentTranscript =
|
||||
serde_json::from_value(encoded).expect("a pre-tree transcript still deserializes");
|
||||
let items = transcript.items();
|
||||
|
||||
assert_eq!(items.len(), 2);
|
||||
assert_eq!(texts(&transcript), vec!["ask", "answer"]);
|
||||
assert_eq!(items[0].parent_id, None, "the first entry is a root");
|
||||
assert_eq!(
|
||||
items[1].parent_id.as_ref(),
|
||||
Some(&items[0].id),
|
||||
"migration links the chain, so a legacy transcript can be branched"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user